From 4d2e4a9be1258a1f1fb969fa57172e8f82769230 Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Sat, 21 Oct 2023 02:30:16 -0500 Subject: [PATCH 01/17] update saving and some reading of settings --- setup.py | 3 +- sprit/__init__.py | 2 + sprit/__pycache__/sprit_gui.cpython-310.pyc | Bin 78404 -> 79169 bytes sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 166588 -> 167670 bytes sprit/resources/settings/gui_theme.json | 1 + .../settings/instrument_settings.json | 12 ++ .../settings/processing_settings.json | 15 ++ sprit/sprit_gui.py | 65 ++++++-- sprit/sprit_hvsr.py | 142 +++++++++++++++++- sprit/sprit_utils.py | 4 +- 10 files changed, 223 insertions(+), 21 deletions(-) create mode 100644 sprit/resources/settings/gui_theme.json create mode 100644 sprit/resources/settings/instrument_settings.json create mode 100644 sprit/resources/settings/processing_settings.json diff --git a/setup.py b/setup.py index 8750d49..3b9f9de 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ author= "Riley Balikian", author_email = "balikian@illinois.edu", version="0.1.50", - package_data={'sprit': ['resources/*', 'resources/icon/*', 'resources/themes/*', 'resources/themes/forest-dark/*', 'resources/themes/forest-light/*', 'resources/sample_data/*',]}, + package_data={'sprit': ['resources/*', 'resources/icon/*', 'resources/themes/*', 'resources/themes/forest-dark/*', + 'resources/themes/forest-light/*', 'resources/sample_data/*','resources/settings/*']}, long_description_content_type="text/markdown", long_description=long_description, packages=find_packages(), diff --git a/sprit/__init__.py b/sprit/__init__.py index 8ba1844..3ebff50 100644 --- a/sprit/__init__.py +++ b/sprit/__init__.py @@ -20,6 +20,7 @@ process_hvsr, plot_hvsr, remove_noise, + save_settings, check_peaks, get_report, HVSRData, @@ -63,6 +64,7 @@ 'process_hvsr', 'plot_hvsr', 'remove_noise', + 'save_settings', 'check_peaks', 'get_report', 'HVSRData', diff --git a/sprit/__pycache__/sprit_gui.cpython-310.pyc b/sprit/__pycache__/sprit_gui.cpython-310.pyc index 81c0f725e49aac389ed1cad9c02e34c5bd035ff6..b1c4f0de13282d84783a260f6837f604bac57578 100644 GIT binary patch delta 22167 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 delta 21379 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 diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index d484c293d2945c77931d2ae9fc7578ef51c6de9c..7128374817ffb2e85586179f9c3d26cc07cb6e48 100644 GIT binary patch delta 39319 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 JW~B;C{~yqC`DFkA diff --git a/sprit/resources/settings/gui_theme.json b/sprit/resources/settings/gui_theme.json new file mode 100644 index 0000000..2582acb --- /dev/null +++ b/sprit/resources/settings/gui_theme.json @@ -0,0 +1 @@ +{"theme_name": "alt"} \ No newline at end of file diff --git a/sprit/resources/settings/instrument_settings.json b/sprit/resources/settings/instrument_settings.json new file mode 100644 index 0000000..fe9c14f --- /dev/null +++ b/sprit/resources/settings/instrument_settings.json @@ -0,0 +1,12 @@ +{ + "instrument":"Raspberry Shake", + "network":"AM", + "station":"RAC84", + "loc":"00", + "channels":["EHZ", "EHE", "EHN"], + "depth":0, + "metapath":"", + "hvsr_band":[0.4, 40], + "instrument_info":{"version":9, + "manufacturer":"Raspberry Shake, S.A."} +} \ No newline at end of file diff --git a/sprit/resources/settings/processing_settings.json b/sprit/resources/settings/processing_settings.json new file mode 100644 index 0000000..6d80c14 --- /dev/null +++ b/sprit/resources/settings/processing_settings.json @@ -0,0 +1,15 @@ +{ + "input_params":{}, + "export_data":{}, + "import_data":{}, + "gui":{}, + "fetch_data":{}, + "batch_data_read":{}, + "generate_ppsds":{}, + "process_hvsr":{}, + "plot_hvsr":{}, + "remove_noise":{}, + "save_settings":{}, + "check_peaks":{}, + "get_report":{} +} \ No newline at end of file diff --git a/sprit/sprit_gui.py b/sprit/sprit_gui.py index 6e2c700..8458ebc 100644 --- a/sprit/sprit_gui.py +++ b/sprit/sprit_gui.py @@ -3,6 +3,7 @@ import datetime import functools import linecache +import json import os import pathlib import pkg_resources @@ -34,6 +35,14 @@ pass global spritApp +global current_theme_name + +resource_dir = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/')) +settings_dir = resource_dir.joinpath('settings') +gui_theme_file = settings_dir.joinpath('gui_theme.json') +with open(gui_theme_file, 'r') as f: + curr_gui_dict = json.load(f) +current_theme_name = curr_gui_dict['theme_name'] #Decorator that catches errors and warnings (to be modified later for gui) def catch_errors(func): @@ -151,10 +160,11 @@ def __init__(self, master): self.darkthemepath = pathlib.Path(pkg_resources.resource_filename(__name__, "resources/themes/forest-dark.tcl")) self.lightthemepath = pathlib.Path(pkg_resources.resource_filename(__name__, "resources/themes/forest-light.tcl")) + + # Create the style object self.style = ttk.Style(master) - self.master.tk.call('source', self.lightthemepath) - #self.style.theme_use('default') + # #self.style.theme_use('forest-light') self.create_menubar() @@ -164,6 +174,13 @@ def __init__(self, master): self.master.columnconfigure(0, weight=1) + if 'forest' in current_theme_name: + if 'light' in current_theme_name: + self.master.tk.call('source', self.lightthemepath) + else: + self.master.tk.call('source', self.darkthemepath) + else: + self.style.theme_use(current_theme_name) # Create the dark theme #self.style.theme_create("dark", parent="alt", settings={ # "TLabel": {"configure": {"background": "black", "foreground": "white"}}, @@ -211,7 +228,33 @@ def create_menubar(self): def on_theme_select(): # Set the theme based on the selected value self.style = ttk.Style() - + + #Update the theme file so the new theme opens on reboot + prev_theme = curr_gui_dict['theme_name'] + curr_gui_dict['theme_name'] = self.theme_var.get() + with open(gui_theme_file, 'w') as f: + json.dump(curr_gui_dict, f) + + def apply_theme(): + if 'forest' in self.theme_var.get(): + if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): + self.master.tk.call('source', self.darkthemepath) + elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): + self.master.tk.call('source', self.lightthemepath) + self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + + if curr_gui_dict['theme_name']=='forest-light' or curr_gui_dict['theme_name'] == 'forest-dark': + do_reboot = messagebox.askyesno('App Restart Required', + f"It is recommended to restart the SpRIT GUI at this time to apply this theme. If not, you may continue but theme errors may occur. Click No to retain current theme ({prev_theme}) \nReboot now?", + ) + print(do_reboot) + if do_reboot: + reboot_app() + else: + self.theme_var.set(prev_theme) + else: + apply_theme() + """An attempt to get the backgrounds right def apply_to_all_children(widget, func): Recursively apply a function to all child widgets of a given widget @@ -229,12 +272,7 @@ def change_background_color(widget): apply_to_all_children(self.master, change_background_color) """ - if 'forest' in self.theme_var.get(): - if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): - self.master.tk.call('source', self.darkthemepath) - elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): - self.master.tk.call('source', self.lightthemepath) - self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + #self.master.tk.call("ttk::setTheme", self.theme_var.get()) #self.style.theme_use(self.theme_var.get()) @@ -250,7 +288,7 @@ def export_parameters(self): filepath = filedialog.asksaveasfilename() self.theme_menu = tk.Menu(self.menubar, tearoff=0) - self.theme_var = tk.StringVar(value="Default") + self.theme_var = tk.StringVar(value=current_theme_name) self.theme_menu.add_radiobutton(label="Default", variable=self.theme_var, value="default", command=on_theme_select) self.theme_menu.add_radiobutton(label="Clam", variable=self.theme_var, value="clam", command=on_theme_select) self.theme_menu.add_radiobutton(label="Alt", variable=self.theme_var, value="alt", command=on_theme_select) @@ -3036,6 +3074,13 @@ def on_closing(): root.destroy() exit() +def reboot_app(): + """Restarts the current program. + Note: this function does not return. Any cleanup action (like + saving data) must be done before calling this function.""" + python = sys.executable + os.execl(python, python, * sys.argv) + if __name__ == "__main__": can_gui = sprit_utils.check_gui_requirements() diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index 5e616fd..2673584 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -48,7 +48,11 @@ max_rank = 0 plotRows = 4 -sample_data_dir = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/sample_data/')) +#Get the main resources directory path, and the other paths as well +resource_dir = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/')) +sample_data_dir = resource_dir.joinpath('sample_data') +settings_dir = resource_dir.joinpath('settings') + sampleFileKeyMap = {'1':sample_data_dir.joinpath('SampleHVSRSite1_AM.RAC84.00.2023.046_2023-02-15_1704-1734.MSEED'), '2':sample_data_dir.joinpath('SampleHVSRSite2_AM.RAC84.00.2023-02-15_2132-2200.MSEED'), '3':sample_data_dir.joinpath('SampleHVSRSite3_AM.RAC84.00.2023.199_2023-07-18_1432-1455.MSEED'), @@ -282,7 +286,9 @@ def export(self, export_path=None, ext='hvsr'): Parameters ---------- export_path : filepath, default=True - Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True + Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). + By default True. + If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True ext : str, optional The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension. """ @@ -711,6 +717,10 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose """ orig_args = locals().copy() #Get the initial arguments + hvsr_data['processing_parameters']['check_peaks'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['check_peaks'][key] = value + if (verbose and 'input_params' not in hvsr_data.keys()) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: pass @@ -994,6 +1004,12 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr #Select which instrument we are reading from (requires different processes for each instrument) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + #Get any kwargs that are included in obspy.read + obspyReadKwargs = {} + for argName in inspect.getfullargspec(obspy.read)[0]: + if argName in kwargs.keys(): + obspyReadKwargs[argName] = kwargs[argName] + #Select how reading will be done if source=='raw': if inst.lower() in raspShakeInstNameList: @@ -1012,28 +1028,27 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr for f in temp_file_glob: currParams = params currParams['datapath'] = f + curr_data = fetch_data(params, source='file', #all the same as input, except just reading the one file using the source='file' - trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs), + trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs) + curr_data.merge() obspyFiles[f.stem] = curr_data #Add path object to dict, with filepath's stem as the site name return HVSRBatch(obspyFiles) - elif source=='file' and str(params['datapath']).lower() not in sampleList: if isinstance(dPath, list) or isinstance(dPath, tuple): rawStreams = [] for datafile in dPath: - rawStream = obspy.read(datafile) + rawStream = obspy.read(datafile, **obspyReadKwargs) rawStreams.append(rawStream) #These are actually streams, not traces - for i, stream in enumerate(rawStreams): if i == 0: rawDataIN = obspy.Stream(stream) #Just in case else: rawDataIN = rawDataIN + stream #This adds a stream/trace to the current stream object - elif str(dPath)[:6].lower()=='sample': pass else: - rawDataIN = obspy.read(dPath)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) + rawDataIN = obspy.read(dPath, **obspyReadKwargs)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) import warnings with warnings.catch_warnings(): warnings.simplefilter(action='ignore', category=UserWarning) @@ -1080,6 +1095,7 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr except: RuntimeError(f'source={source} not recognized, and datapath cannot be read using obspy.read()') + #Get metadata from the data itself, if not reading raw data try: dataIN = rawDataIN.copy() if source!='raw': @@ -1191,6 +1207,7 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr #Remerge data dataIN = dataIN.merge(method=1) + #Plot the input stream? if plot_input_stream: try: params['InputPlot'] = _plot_specgram_stream(stream=dataIN, params=params, component='Z', stack_type='linear', detrend='mean', dbscale=True, fill_gaps=None, ylimstd=3, return_fig=True, fig=None, ax=None, show_plot=False) @@ -1205,6 +1222,7 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr else: dataIN = _sort_channels(input=dataIN, source=source, verbose=verbose) + #Clean up the ends of the data unless explicitly specified to do otherwise (this is a kwarg, not a parameter) if 'clean_ends' not in kwargs.keys(): clean_ends=True else: @@ -1492,6 +1510,11 @@ def convert_to_mpl_dates(obspyUTCDateTime): hvsr_data = sprit_utils.make_it_classy(hvsr_data) + hvsr_data['processing_parameters'] = {} + hvsr_data['processing_parameters']['generate_ppsds'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['generate_ppsds'][key] = value + hvsr_data['ProcessingStatus']['PPSDStatus'] = True hvsr_data = _check_processing_status(hvsr_data) return hvsr_data @@ -1587,6 +1610,11 @@ def get_report(hvsr_results, report_format='print', plot_type='HVSR p ann C+ p a #Curve pass? orig_args = locals().copy() #Get the initial arguments + hvsr_results['processing_parameters']['get_report'] = {} + for key, value in orig_args.items(): + hvsr_results['processing_parameters']['get_report'][key] = value + + if (verbose and isinstance(hvsr_results, HVSRBatch)) or (verbose and not hvsr_results['batch']): if isinstance(hvsr_results, HVSRData) and hvsr_results['batch']: pass @@ -2106,6 +2134,7 @@ def input_params(datapath, metapath = '', hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], + instrument_settings=None, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -2156,6 +2185,10 @@ def input_params(datapath, Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. + instrument_settings : None, str, default=None + The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. + If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). + The default settings can be reset using the save_settings() function with the parameter settings_path='default'. verbose : bool, default=False Whether to print output and results to terminal @@ -2299,6 +2332,24 @@ def input_params(datapath, 'ProcessingStatus':{'InputStatus':True, 'OverallStatus':True} } + #Replace any default parameter settings with those from json file of interest, potentially + if instrument_settings is None: + instrument_settings_dict = {} + elif instrument_settings == "default" or instrument_settings is True: + # Update inputParamDict with default file + default_settings_json = settings_dir.joinpath('instrument_settings.json') + with open(default_settings_json.as_posix(), 'r') as f: + instrument_settings_dict = json.load(f) + else: + # Update inputParamDict with file + with open(instrument_settings, 'r') as f: + instrument_settings_dict = json.load(f) + + for settingName in instrument_settings_dict.keys(): + if settingName in inputParamDict.keys(): + inputParamDict[settingName] = instrument_settings_dict[settingName] + + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True params = _check_processing_status(params) @@ -2760,6 +2811,10 @@ def process_hvsr(hvsr_data, method=3, smooth=True, freq_smooth='konno ohmachi', hvsr_out['ProcessingStatus']['HVStatus'] = True hvsr_out = _check_processing_status(hvsr_out) + hvsr_data['processing_parameters']['process_hvsr'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['process_hvsr'][key] = value + return hvsr_out #Function to remove noise windows from data @@ -3042,6 +3097,77 @@ def remove_outlier_curves(params, outlier_std=3, ppsd_length=30): params['ppsds'][k]['current_times_used'] = np.delete(params['ppsds'][k]['current_times_used'], index, axis=0) return params +###WORKING ON THIS +#Save default instrument and processing settings to json file(s) +def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False): + """Save settings to json file + + Parameters + ---------- + settings_path : str, default="default" + Where to save the json file(s) containing the settings, by default 'default'. + If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. + If 'all' is selected, a directory should be supplied. + Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory. + settings_type : str, {'all', 'instrument', 'processing'} + What kind of settings to save. + If 'all', saves all possible types in their respective json files. + If 'instrument', save the instrument settings to their respective file. + If 'processing', saves the processing settings to their respective file. By default 'all' + verbose : bool, default=True + Whether to print outputs and information to the terminal + + """ + fnameDict = {} + fnameDict['instrument'] = "instrument_settings.json" + fnameDict['processing'] = "processing_settings.json" + + if settings_path == 'default' or settings_path is True: + settingsPath = resource_dir + else: + settings_path = pathlib.Path(settings_path) + if not settings_path.exists(): + if not settings_path.parent.exists(): + print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}') + settingsPath = pathlib.Path.home() + else: + settingsPath = settings_path.parent + + if settings_path.is_dir(): + settingsPath = settings_path + elif settings_path.is_file(): + settingsPath = settings_path.parent + fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json" + fnameDict['processing'] = settings_path.name+"_processingSettings.json" + + #Get final filepaths + instSetFPath = settingsPath.joinpath(fnameDict['instrument']) + procSetFPath = settingsPath.joinpath(fnameDict['processing']) + + #Get settings values + instKeys = ["instrument", "network", "station", "loc", "channels", "depth", "metapath", "hvsr_band"] + procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report] + + instrument_settings_dict = {} + processing_settings_dict = {} + + for k in instKeys: + instrument_settings_dict[k] = hvsr_data[k] + + for func in procFuncs: + funcName = func.__name__ + processing_settings_dict[funcName] = {} + for arg in inspect.getfullargspec(func)[0]: + processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg] + + #Save settings files + if settings_type.lower()=='instrument' or settings_type.lower()=='all': + with open(instSetFPath.as_posix(), 'w') as instSetF: + json.dump(instrument_settings_dict, instSetF) + if settings_type.lower()=='processing' or settings_type.lower()=='all': + with open(procSetFPath.as_posix(), 'w') as procSetF: + json.dump(processing_settings_dict, procSetF) + #Read data as batch def batch_data_read(input_data, batch_type='table', param_col=None, batch_params=None, verbose=False, **readcsv_getMeta_fetch_kwargs): """Function to read data in data as a batch of multiple data files. This is best used through sprit.fetch_data(*args, source='batch', **other_kwargs). diff --git a/sprit/sprit_utils.py b/sprit/sprit_utils.py index 892006a..99bfe6f 100644 --- a/sprit/sprit_utils.py +++ b/sprit/sprit_utils.py @@ -19,7 +19,7 @@ channel_order = {'Z': 0, '1': 1, 'N': 1, '2': 2, 'E': 2} def check_gui_requirements(): - print("Checking requirements for gui") + #First, check requirements # Define a command that tries to open a window command = "python -c \"import tkinter; tkinter.Tk()\"" @@ -32,7 +32,7 @@ def check_gui_requirements(): oktoproceed=True else: oktoproceed=False - print("GUI window could not be created.") + print("GUI window cannot be created.") return oktoproceed From 517c82abfa438515e0163b7f6cf46ccad3593fae Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:33:53 -0500 Subject: [PATCH 02/17] save_settings working now --- sprit/__pycache__/__init__.cpython-310.pyc | Bin 1300 -> 1329 bytes sprit/__pycache__/sprit_gui.cpython-310.pyc | Bin 79169 -> 79800 bytes sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 167670 -> 170332 bytes sprit/__pycache__/sprit_utils.cpython-310.pyc | Bin 8897 -> 8978 bytes .../settings/processing_settings.json | 7 ++--- sprit/sprit_hvsr.py | 26 +++++++++--------- sprit/sprit_utils.py | 5 ++++ 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/sprit/__pycache__/__init__.cpython-310.pyc b/sprit/__pycache__/__init__.cpython-310.pyc index bda4ac573cb4712333f019831ad3998ceeb74c80..672c9e7f6e6a52a4292503e648cbe8fb3bb9e019 100644 GIT binary patch delta 288 zcmbQjwULWApO=@50SK51j8nHvqlew54E34e%DlRC>EQ!x3D=sQx0-9MQ0wN?pgbauf zo-DvD%P2WnkJ*&>7B@t3X-Q^I@#Gv9bER9H5RvrK%pzH!@+!Wd%$(Fp1*gQE% zJWbWf=a^-U<$+RB{9vVe2n`A#Q2}I8uq)(%!bMCVYbRT?lCbnk9zI OgULjghmVDoLk$4dq)3MV diff --git a/sprit/__pycache__/sprit_gui.cpython-310.pyc b/sprit/__pycache__/sprit_gui.cpython-310.pyc index b1c4f0de13282d84783a260f6837f604bac57578..adb14eb334c41ec6569465362fa5451c9affe0f3 100644 GIT binary patch delta 19774 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 M1bo^01={rg1zN|z=l}o! delta 19010 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 diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 7128374817ffb2e85586179f9c3d26cc07cb6e48..5e28c6c566cf9ad40998acd9c5d12c50ac11b4ce 100644 GIT binary patch delta 30522 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 bVQ=3nk8|rfzN4|Lv8QH-=FGnIO*``+_70xW delta 603 zcmX|-yKfUg5XNuz9<~qLiHwAd!S>ok6ca%|fMSzK;jl+SWgj94!B$rZJ8f$b2}wh-^@yO;kWN`wVza&c zjb*2`>Uv9*Igi^ebJiV?2XxVAbgAu%Lzt@3+xPF#j_cWehqnConoT{Q(?S*@|aywm4{H_=Tu3NDjXzJ_4qfr>>=p7-}pbzGk<@ zSSj_a{&$TK$6p~B>LTeAmJF4Mc>4d9NC>RRH5>MHZiGr7kfaa$82?rxv%TCs%XX;C zX=}|2f-Vi|(}-59S20y$Y(hRu5Tp1^voUwm;?^iJ)2uY+>K1P;v!v9k5=}%!ePFuY zMw>6#Xk-`hN`$$ojC5td0F!8t1r1nd_#?MN)Db)pZvF_8V!BX;ln4qncp=^uY}gWG z#VouMlf~;$7b`^ Date: Sat, 21 Oct 2023 21:41:54 -0500 Subject: [PATCH 03/17] update filelist support --- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 170332 -> 170510 bytes sprit/sprit_hvsr.py | 29 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 5e28c6c566cf9ad40998acd9c5d12c50ac11b4ce..92642aff0f8e0b46f674d25bceaf04f76ece7795 100644 GIT binary patch delta 16744 zcmbVz34B!5_5a+LnaREZAq!jB0+W!Cu!C$NVUsOL1Q~@eWjdtF=EBr6|^=>d;!XREoPIR

te9sFE+V=ndfBx9{=Dxd}d+xdC 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 diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index 2e52c86..9327b93 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -951,7 +951,8 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr #Make sure datapath is pointing to an actual file if isinstance(params['datapath'],list): for i, d in enumerate(params['datapath']): - params['datapath'][i] = sprit_utils.checkifpath(str(d).strip()) + params['datapath'][i] = sprit_utils.checkifpath(str(d).strip(), sample_list=sampleList) + dPath = params['datapath'] else: dPath = sprit_utils.checkifpath(params['datapath'], sample_list=sampleList) @@ -1102,6 +1103,8 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr #Use metadata from file for; # site if params['site'] == "HVSR Site": + if isinstance(dPath, (list, tuple)): + dPath = dPath[0] params['site'] = dPath.stem params['params']['site'] = dPath.stem @@ -1138,11 +1141,13 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr today_Starttime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=0, minute=0, second=0, microsecond=0)) - maxStarttime = datetime.time(hour=0, minute=0, second=0, microsecond=0) + maxStarttime = datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, day=params['acq_date'].day, + hour=0, minute=0, second=0, microsecond=0, tzinfo=datetime.timezone.utc) if str(params['starttime']) == str(today_Starttime): for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, - second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond) + currTime = datetime.datetime(year=tr.stats.starttime.year, month=tr.stats.starttime.month, day=tr.stats.starttime.day, + hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, + second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond, tzinfo=datetime.timezone.utc) if currTime > maxStarttime: maxStarttime = currTime @@ -1157,17 +1162,19 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr today_Endtime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=23, minute=59, second=59, microsecond=999999)) - minEndtime = datetime.time(hour=23, minute=59, second=59, microsecond=999999) - if str(params['endtime']) == str(today_Endtime): + tomorrow_Endtime = today_Endtime + (60*60*24) + minEndtime = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)#(hour=23, minute=59, second=59, microsecond=999999) + if str(params['endtime']) == str(today_Endtime) or str(params['endtime'])==tomorrow_Endtime: for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, - second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond) + currTime = datetime.datetime(year=tr.stats.endtime.year, month=tr.stats.endtime.month, day=tr.stats.endtime.day, + hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, + second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond, tzinfo=datetime.timezone.utc) if currTime < minEndtime: minEndtime = currTime - newEndtime = obspy.UTCDateTime(datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, - day = params['acq_date'].day, + newEndtime = obspy.UTCDateTime(datetime.datetime(year=minEndtime.year, month=minEndtime.month, + day = minEndtime.day, hour=minEndtime.hour, minute=minEndtime.minute, - second=minEndtime.second, microsecond=minEndtime.microsecond)) + second=minEndtime.second, microsecond=minEndtime.microsecond, tzinfo=datetime.timezone.utc)) params['endtime'] = newEndtime params['params']['endtime'] = newEndtime From d812ec8554685d9efe88ee257d1fc831b05d098c Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Wed, 25 Oct 2023 00:33:32 -0500 Subject: [PATCH 04/17] fix specgram chart, add tromino (exp.) --- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 170510 -> 175092 bytes sprit/sprit_hvsr.py | 153 ++++++++++++++++++- 2 files changed, 145 insertions(+), 8 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 92642aff0f8e0b46f674d25bceaf04f76ece7795..4d4161f52112ef1110097c961c34098356a4a08d 100644 GIT binary patch delta 25247 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⁢@^XD4_BrogNaD{cK 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 delta 21081 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 diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index 9327b93..e0a4ac0 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -14,6 +14,7 @@ import pathlib import pickle import pkg_resources +import struct import tempfile import traceback import warnings @@ -938,7 +939,9 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr date=params['acq_date'] #Cleanup for gui input - if '}' in str(params['datapath']): + if isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass + elif '}' in str(params['datapath']): params['datapath'] = params['datapath'].as_posix().replace('{','') params['datapath'] = params['datapath'].split('}') @@ -953,6 +956,8 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr for i, d in enumerate(params['datapath']): params['datapath'][i] = sprit_utils.checkifpath(str(d).strip(), sample_list=sampleList) dPath = params['datapath'] + elif isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass else: dPath = sprit_utils.checkifpath(params['datapath'], sample_list=sampleList) @@ -1004,6 +1009,7 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr #Select which instrument we are reading from (requires different processes for each instrument) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'tromino 3g', 'tromino 3g+', 'tr', 't'] #Get any kwargs that are included in obspy.read obspyReadKwargs = {} @@ -1013,12 +1019,16 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr #Select how reading will be done if source=='raw': - if inst.lower() in raspShakeInstNameList: - try: + try: + if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) - except: - raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") - return params + + elif inst.lower() in trominoNameList: + rawDataIN = __read_tromino_files(dPath, params, verbose=verbose) + except: + raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") + elif source=='stream' or isinstance(params, (obspy.Stream, obspy.Trace)): + rawDataIN = params['datapath'].copy() elif source=='dir': if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) @@ -1548,14 +1558,57 @@ def get_metadata(params, write_path='', update_metadata=True, source=None, **rea params : dict Modified input dictionary with additional key:value pair containing paz dictionary (key = "paz") """ - invPath = params['metapath'] raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'trm', 't'] if params['instrument'].lower() in raspShakeInstNameList: if update_metadata: params = _update_shake_metadata(filepath=invPath, params=params, write_path=write_path) params = _read_RS_Metadata(params, source=source) + elif params['instrument'].lower() in trominoNameList: + params['paz'] = {'Z':{}, 'E':{}, 'N':{}} + #ALL THESE VALUES ARE PLACEHOLDERS, SAME AS RASPBERRY SHAKE! + params['paz']['Z'] = {'sensitivity': 360000000.0, + 'gain': 360000000.0, + 'poles': [(-1+0j), (-3.03+0j), (-3.03+0j), (-666.67+0j)], + 'zeros': [0j, 0j, 0j]} + params['paz']['E'] = params['paz']['Z'] + params['paz']['N'] = params['paz']['Z'] + + channelObj_Z = obspy.core.inventory.channel.Channel(code='BHZ', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=90, types=None, external_references=None, + sample_rate=None, sample_rate_ratio_number_samples=None, sample_rate_ratio_number_seconds=None, + storage_format=None, clock_drift_in_seconds_per_sample=None, calibration_units=None, + calibration_units_description=None, sensor=None, pre_amplifier=None, data_logger=None, + equipments=None, response=None, description=None, comments=None, start_date=None, end_date=None, + restricted_status=None, alternate_code=None, historical_code=None, data_availability=None, + identifiers=None, water_level=None, source_id=None) + channelObj_E = obspy.core.inventory.channel.Channel(code='BHE', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=90, dip=0) + + channelObj_N = obspy.core.inventory.channel.Channel(code='BHN', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=0) + + siteObj = obspy.core.inventory.util.Site(name=params['params']['site'], description=None, town=None, county=None, region=None, country=None) + stationObj = obspy.core.inventory.station.Station(code='TZ', latitude=params['params']['latitude'], longitude=params['params']['longitude'], + elevation=params['params']['elevation'], channels=[channelObj_Z, channelObj_E, channelObj_N], site=siteObj, + vault=None, geology=None, equipments=None, operators=None, creation_date=datetime.datetime.today(), + termination_date=None, total_number_of_channels=None, + selected_number_of_channels=None, description='Estimated data for Tromino, this is NOT from the manufacturer', + comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, water_level=None, source_id=None) + + network = [obspy.core.inventory.network.Network(code='TROM', stations=[stationObj], total_number_of_stations=None, + selected_number_of_stations=None, description=None, comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, operators=None, source_id=None)] + + params['inv'] = obspy.Inventory(networks=network) else: if not invPath: pass #if invPath is None @@ -4033,6 +4086,88 @@ def __read_RS_file_struct(datapath, source, year, doy, inv, params, verbose=Fals return rawDataIN +#Read data from Tromino +def __read_tromino_files(datapath, params, verbose=False): + """Function to read data from tromino. Specifically, this has been lightly tested on Tromino 3G+ machines + + Parameters + ---------- + datapath : str, pathlib.Path() + The input parameter _datapath_ from sprit.input_params() + params : HVSRData or HVSRBatch + The parameters as read in from input_params() and and fetch_data() + verbose : bool, optional + Whether to print results to terminal, by default False + + Returns + ------- + obspy.Stream + An obspy.Stream object containing the trace data from the Tromino instrument + """ + dPath = datapath + + strucSizes = {'c':1, 'b':1,'B':1, '?':1, + 'h':2,'H':2,'e':2, + 'i':4,'I':4,'l':4,'L':4,'f':4, + 'q':8,'Q':8,'d':8, + 'n':8,'N':8,'s':16,'p':16,'P':16,'x':16} + + #H (pretty sure it's Q) I L or Q all seem to work (probably not Q?) + structFormat = 'H' + structSize = strucSizes[structFormat] + + dataList = [] + with open(dPath, 'rb') as f: + while True: + data = f.read(structSize) # Read 4 bytes + if not data: # End of file + break + value = struct.unpack(structFormat, data)[0] # Interpret as a float + dataList.append(value) + + import numpy as np + dataArr = np.array(dataList) + import matplotlib.pyplot as plt + + medVal = np.nanmedian(dataArr[50000:100000]) + + startByte=24576 + comp1 = dataArr[startByte::3] - medVal + comp2 = dataArr[startByte+1::3] - medVal + comp3 = dataArr[startByte+2::3] - medVal + headerBytes = dataArr[:startByte] + + #fig, ax = plt.subplots(3, sharex=True, sharey=True) + #ax[0].plot(comp1, linewidth=0.1, c='k') + #ax[1].plot(comp2, linewidth=0.1, c='k') + #ax[2].plot(comp3, linewidth=0.1, c='k') + + sTime = obspy.UTCDateTime(params['acq_date'].year, params['acq_date'].month, params['acq_date'].day, + params['starttime'].hour, params['starttime'].minute, + params['starttime'].second,params['starttime'].microsecond) + eTime = sTime + (((len(comp1))/128)/60)*60 + + traceHeader1 = {'sampling_rate':128, + 'calib' : 1, + 'npts':len(comp1), + 'network':'AM', + 'location':'00', + 'station' : 'TRMNO', + 'channel':'BHE', + 'starttime':sTime} + + traceHeader2=traceHeader1.copy() + traceHeader3=traceHeader1.copy() + traceHeader2['channel'] = 'BHN' + traceHeader3['channel'] = 'BHZ' + + trace1 = obspy.Trace(data=comp1, header=traceHeader1) + trace2 = obspy.Trace(data=comp2, header=traceHeader2) + trace3 = obspy.Trace(data=comp3, header=traceHeader3) + + st = obspy.Stream([trace1, trace2, trace3]) + return st + ##Helper functions for remove_noise() #Helper function for removing gaps def __remove_gaps(stream, window_gaps_obspy): @@ -5588,7 +5723,9 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix ax.zorder=1 ax.set_facecolor('#ffffff00') #Create transparent background for front axis #plt.sca(axy) - im = ax.imshow(psdArr, origin='lower', extent=extList, aspect='auto', interpolation='nearest', **kwargs) + psdArr = np.flip(psdArr, axis=0) + print(extList) + im = axy.imshow(psdArr, origin='lower', extent=extList, aspect='auto', interpolation='nearest', **kwargs) ax.tick_params(left=False, right=False) #plt.sca(ax) if peak_plot: From 24138d7df5aa31a425c17c839639a478f6b55b72 Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:50:50 -0500 Subject: [PATCH 05/17] updates to noise removal (works now) --- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 175092 -> 175327 bytes sprit/__pycache__/sprit_utils.cpython-310.pyc | Bin 8978 -> 9060 bytes sprit/sprit_hvsr.py | 83 +++++++++++------- sprit/sprit_utils.py | 5 ++ 4 files changed, 54 insertions(+), 34 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 4d4161f52112ef1110097c961c34098356a4a08d..461c8dd5ab025d361c6805793380ba5802e9faa0 100644 GIT binary patch delta 17828 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 delta 17708 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 PK6#x|JgW$o7atD*ZrD@2 delta 316 zcmaFjHpz`IpO=@50SH>vOj3W?ZR87=ROkY7OBiYxN*J@4fS5UrshO$BpoU=q%R&Yh zhFJN@t&%pA|4WK$IG3=cFfCv&;izF~W(1O~&5R3~ni&}xYFL9AG@1Qw@nq%|l$OL# zc9Gg3Cjm57h*5x%1BiKmn5BV@k&B6iQG$_$v8ZaZob*g4#>&k*WStoqTPFXJOJVGq zoG35B*gLsO-jUIL@*eq2#(A676a*NhCo(cH6bk^2<746gY6f93AO;$?m diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index e0a4ac0..99a3ceb 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -571,7 +571,6 @@ def run(datapath, source='file', verbose=False, **kwargs): except: #Even if batch, this is reading in data for all sites so we want to raise error, not just warn raise RuntimeError('Data not read correctly, see sprit.fetch_data() function and parameters for more details.') - #Remove Noise try: remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in remove_noise.__code__.co_varnames} @@ -677,8 +676,9 @@ def run(datapath, source='file', verbose=False, **kwargs): #We do not need to plot another report if already plotted pass else: - hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} - hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, **hvplot_kwargs) + #hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} + #hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, return_fig=True, show=False, close_figs=True) + pass else: pass @@ -1374,7 +1374,7 @@ def get_default_args(func): hvsr_data[site_name]['ProcessingStatus']['OverallStatus'] = False return hvsr_data else: - paz=hvsr_data['paz'] + paz = hvsr_data['paz'] stream = hvsr_data['stream'] #Get ppsds of e component @@ -1509,10 +1509,18 @@ def convert_to_mpl_dates(obspyUTCDateTime): hvsrDF['TimesProcessed_MPLEnd'] = hvsrDF['TimesProcessed_MPL'] + (ppsd_kwargs['ppsd_length']/86400) hvsrDF['Use'] = True + hvsrDF['Use']=hvsrDF['Use'].astype(bool) for gap in hvsr_data['ppsds']['Z']['times_gaps']: - hvsrDF['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ - (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ - + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'].gt(gap[1].matplotlib_date))| \ + (hvsrDF['TimesProcessed_MPLEnd'].lt(gap[0].matplotlib_date))# | \ + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + if 'xwindows_out' in hvsr_data.keys(): + for window in hvsr_data['xwindows_out']: + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].lt(window[0]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].lt(window[0]) )| \ + (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].gt(window[1]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].gt(window[1])) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + hvsrDF.set_index('TimesProcessed', inplace=True) hvsr_data['hvsr_df'] = hvsrDF #Create dict entry to keep track of how many outlier hvsr curves are removed (2-item list with [0]=current number, [1]=original number of curves) @@ -1568,14 +1576,14 @@ def get_metadata(params, write_path='', update_metadata=True, source=None, **rea params = _read_RS_Metadata(params, source=source) elif params['instrument'].lower() in trominoNameList: params['paz'] = {'Z':{}, 'E':{}, 'N':{}} - #ALL THESE VALUES ARE PLACEHOLDERS, SAME AS RASPBERRY SHAKE! + #ALL THESE VALUES ARE PLACEHOLDERS, taken from RASPBERRY SHAKE! (Needed for PPSDs) params['paz']['Z'] = {'sensitivity': 360000000.0, 'gain': 360000000.0, 'poles': [(-1+0j), (-3.03+0j), (-3.03+0j), (-666.67+0j)], 'zeros': [0j, 0j, 0j]} params['paz']['E'] = params['paz']['Z'] params['paz']['N'] = params['paz']['Z'] - + channelObj_Z = obspy.core.inventory.channel.Channel(code='BHZ', location_code='00', latitude=params['params']['latitude'], longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], azimuth=0, dip=90, types=None, external_references=None, @@ -1674,7 +1682,6 @@ def get_report(hvsr_results, report_format='print', plot_type='HVSR p ann C+ p a for key, value in orig_args.items(): hvsr_results['processing_parameters']['get_report'][key] = value - if (verbose and isinstance(hvsr_results, HVSRBatch)) or (verbose and not hvsr_results['batch']): if isinstance(hvsr_results, HVSRData) and hvsr_results['batch']: pass @@ -2806,7 +2813,6 @@ def process_hvsr(hvsr_data, method=3, smooth=True, freq_smooth='konno ohmachi', bool_col='Use' eval_col='HV_Curves' - testCol = hvsr_out['hvsr_df'].loc[hvsr_out['hvsr_df'][bool_col], eval_col].apply(np.nanstd).gt((avg_stdT + (std_stdT * outlier_curve_std))) low_std_val = avg_stdT - (std_stdT * outlier_curve_std) hi_std_val = avg_stdT + (std_stdT * outlier_curve_std) @@ -3034,12 +3040,15 @@ def remove_noise(hvsr_data, remove_method='auto', sat_percent=0.995, noise_perce #Add output if isinstance(output, (HVSRData, dict)): - output['stream'] = outStream + if isinstance(outStream, (obspy.Stream, obspy.Trace)): + output['stream'] = outStream + else: + output['stream'] = outStream['stream'] output['input_stream'] = hvsr_data['input_stream'] output['ProcessingStatus']['RemoveNoiseStatus'] = True output = _check_processing_status(output) - if 'hvsr_df' in output.keys(): + if 'hvsr_df' in output.keys() or ('params' in output.keys() and 'hvsr_df' in output['params'].keys())or ('input_params' in output.keys() and 'hvsr_df' in output['input_params'].keys()): hvsrDF = output['hvsr_df'] outStream = output['stream'].split() @@ -3053,8 +3062,10 @@ def remove_noise(hvsr_data, remove_method='auto', sat_percent=0.995, noise_perce if trEndTime < trStartTime and comp_end==comp_start: gap = [trEndTime,trStartTime] + output['hvsr_df']['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ + output['hvsr_df']['Use'] = output['hvsr_df']['Use'].astype(bool) trEndTime = trace.stats.endtime @@ -3065,8 +3076,10 @@ def remove_noise(hvsr_data, remove_method='auto', sat_percent=0.995, noise_perce else: warnings.warn(f"Output of type {type(output)} for this function will likely result in errors in other processing steps. Returning hvsr_data data.") return hvsr_data - - + + output = sprit_utils.make_it_classy(output) + if 'xwindows_out' not in output.keys(): + output['xwindows_out'] = [] return output #Remove outlier ppsds @@ -3131,16 +3144,19 @@ def remove_outlier_curves(params, outlier_std=3, ppsd_length=30): psds_to_rid.append(i) #Use dataframe - hvsrDF = params['hvsr_df'] + hvsrDF = params['hvsr_df'].copy() psdVals = hvsrDF['psd_values_'+k] - params['hvsr_df'][k+'_CurveMedian'] = psdVals.apply(np.nanmedian) - params['hvsr_df'][k+'_CurveMean'] = psdVals.apply(np.nanmean) + hvsrDF[k+'_CurveMedian'] = psdVals.apply(np.nanmedian) + hvsrDF[k+'_CurveMean'] = psdVals.apply(np.nanmean) - totMean = np.nanmean(params['hvsr_df'][k+'_CurveMean']) - stds[k] = np.nanstd(params['hvsr_df'][k+'_CurveMean']) - - meanArr = params['hvsr_df'][k+'_CurveMean'] - params['hvsr_df']['Use'] = meanArr < (totMean + outlier_std * stds[k]) + totMean = np.nanmean(hvsrDF[k+'_CurveMean']) + stds[k] = np.nanstd(hvsrDF[k+'_CurveMean']) + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + #meanArr = hvsrDF[k+'_CurveMean'].loc[hvsrDF['Use']] + threshVal = totMean + outlier_std * stds[k] + hvsrDF['Use'] = hvsrDF[k+'_CurveMean'][hvsrDF['Use']].lt(threshVal) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) psds_to_rid = np.unique(psds_to_rid) @@ -4701,13 +4717,13 @@ def _select_windows(input): if 'hvsr_curve' in input.keys(): fig, ax = plot_hvsr(hvsr_data=input, plot_type='spec', returnfig=True, cmap='turbo') else: - params = input.copy() - input = input['stream'] + hvsr_data = input#.copy() + input_stream = hvsr_data['stream'] - if isinstance(input, obspy.core.stream.Stream): - fig, ax = _plot_specgram_stream(input, component=['Z']) - elif isinstance(input, obspy.core.trace.Trace): - fig, ax = _plot_specgram_stream(input) + if isinstance(input_stream, obspy.core.stream.Stream): + fig, ax = _plot_specgram_stream(input_stream, component=['Z']) + elif isinstance(input_stream, obspy.core.trace.Trace): + fig, ax = _plot_specgram_stream(input_stream) global lineArtist global winArtist @@ -4731,10 +4747,10 @@ def _select_windows(input): fig.canvas.mpl_connect('close_event', _on_fig_close)#(clickNo, xWindows, pathList, windowDrawn, winArtist, lineArtist, x0, fig, ax)) plt.pause(1) - params['xwindows_out'] = xWindows - params['fig'] = fig - params['ax'] = ax - return params + hvsr_data['xwindows_out'] = xWindows + hvsr_data['fig_noise'] = fig + hvsr_data['ax_noise'] = ax + return hvsr_data #Support function to help select_windows run properly def _on_fig_close(event): @@ -5724,7 +5740,6 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix ax.set_facecolor('#ffffff00') #Create transparent background for front axis #plt.sca(axy) psdArr = np.flip(psdArr, axis=0) - print(extList) im = axy.imshow(psdArr, origin='lower', extent=extList, aspect='auto', interpolation='nearest', **kwargs) ax.tick_params(left=False, right=False) #plt.sca(ax) diff --git a/sprit/sprit_utils.py b/sprit/sprit_utils.py index dcb675e..3875a04 100644 --- a/sprit/sprit_utils.py +++ b/sprit/sprit_utils.py @@ -316,6 +316,11 @@ def make_it_classy(input_data, verbose=False): for kin in input_data['input_params'].keys(): if kin not in input_data.keys(): input_data[kin] = input_data['input_params'][kin] + if k=='params': + for kin in input_data['params'].keys(): + print(kin) + if kin not in input_data.keys(): + input_data[kin] = input_data['params'][kin] output_class = input_data else: output_class = sprit_hvsr.HVSRData(input_data) From 02b13aa28bb16c650e2f7f5a2258683de538b081 Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Fri, 27 Oct 2023 01:52:18 -0500 Subject: [PATCH 06/17] updates to finding the peak --- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 175327 -> 176424 bytes sprit/sprit_hvsr.py | 62 +++++++++++++++---- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 461c8dd5ab025d361c6805793380ba5802e9faa0..3cda950bc5a03b2d10cf66bc52f74c1c0cc9e316 100644 GIT binary patch delta 23565 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 delta 22012 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 N<7LMTTT@c%{{alJN>cy; diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index 99a3ceb..c3f267e 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -552,8 +552,14 @@ def run(datapath, source='file', verbose=False, **kwargs): RuntimeError If the data being processed is a single file, an error will be raised if generate_ppsds() does not work correctly. No errors are raised for remove_noise() errors (since that is an optional step) and the process_hvsr() step (since that is the last processing step) . """ + if 'hvsr_band' not in kwargs.keys(): + kwargs['hvsr_band'] = inspect.signature(input_params).parameters['hvsr_band'].default + if 'peak_freq_range' not in kwargs.keys(): + kwargs['peak_freq_range'] = inspect.signature(input_params).parameters['peak_freq_range'].default + #Get the input parameters - input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in input_params.__code__.co_varnames} + input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(input_params).parameters.keys())} + try: params = input_params(datapath=datapath, verbose=verbose, **input_params_kwargs) except: @@ -566,14 +572,14 @@ def run(datapath, source='file', verbose=False, **kwargs): #Fetch Data try: - fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in fetch_data.__code__.co_varnames} + fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(fetch_data).parameters.keys())} dataIN = fetch_data(params=params, source=source, verbose=verbose, **fetch_data_kwargs) except: #Even if batch, this is reading in data for all sites so we want to raise error, not just warn raise RuntimeError('Data not read correctly, see sprit.fetch_data() function and parameters for more details.') #Remove Noise try: - remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in remove_noise.__code__.co_varnames} + remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(remove_noise).parameters.keys())} data_noiseRemoved = remove_noise(hvsr_data=dataIN, verbose=verbose,**remove_noise_kwargs) except: data_noiseRemoved = dataIN @@ -597,8 +603,8 @@ def run(datapath, source='file', verbose=False, **kwargs): #Generate PPSDs try: - generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in generate_ppsds.__code__.co_varnames} - PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in PPSD.__init__.__code__.co_varnames} + generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(generate_ppsds).parameters.keys())} + PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(PPSD).parameters.keys())} generate_ppsds_kwargs.update(PPSDkwargs) ppsd_data = generate_ppsds(hvsr_data=data_noiseRemoved, verbose=verbose,**generate_ppsds_kwargs) except Exception as e: @@ -624,7 +630,7 @@ def run(datapath, source='file', verbose=False, **kwargs): #Process HVSR Curves try: - process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in process_hvsr.__code__.co_varnames} + process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(process_hvsr).parameters.keys())} hvsr_results = process_hvsr(hvsr_data=ppsd_data, verbose=verbose,**process_hvsr_kwargs) except Exception as e: traceback.print_exception(sys.exc_info()[1]) @@ -654,10 +660,10 @@ def run(datapath, source='file', verbose=False, **kwargs): #Final post-processing/reporting #Check peaks - check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in check_peaks.__code__.co_varnames} + check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(check_peaks).parameters.keys())} hvsr_results = check_peaks(hvsr_data=hvsr_results, verbose=verbose, **check_peaks_kwargs) - get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in get_report.__code__.co_varnames} + get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(get_report).parameters.keys())} get_report(hvsr_results=hvsr_results, verbose=verbose, **get_report_kwargs) if verbose: @@ -697,7 +703,7 @@ def run(datapath, source='file', verbose=False, **kwargs): #Quality checks, stability tests, clarity tests #def check_peaks(hvsr, x, y, index_list, peak, peakm, peakp, hvsr_peaks, stdf, hvsr_log_std, rank, hvsr_band=[0.4, 40], do_rank=False): -def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose=False): +def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_range=[1, 20], verbose=False): """Function to run tests on HVSR peaks to find best one and see if it passes quality checks Parameters @@ -706,6 +712,10 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose Dictionary containing all the calculated information about the HVSR data (i.e., hvsr_out returned from process_hvsr) hvsr_band : tuple or list, default=[0.4, 40] 2-item tuple or list with lower and upper limit of frequencies to analyze + peak_selection : str or numeric, default='max' + How to select the "best" peak used in the analysis. For peak_selection="max" (default value), the highest peak within peak_freq_range is used. + For peak_selection='scored', an algorithm is used to select the peak based in part on which peak passes the most SESAME criteria. + If a numeric value is used (e.g., int or float), this should be a frequency value to manually select as the peak of interest. peak_freq_range : tuple or list, default=[1, 20]; The frequency range within which to check for peaks. If there is an HVSR curve with multiple peaks, this allows the full range of data to be processed while limiting peak picks to likely range. verbose : bool, default=False @@ -757,13 +767,38 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose if hvsr_data['ProcessingStatus']['OverallStatus']: if not hvsr_band: hvsr_band = [0.4,40] + hvsr_data['hvsr_band'] = hvsr_band anyK = list(hvsr_data['x_freqs'].keys())[0] x = hvsr_data['x_freqs'][anyK] #Consistent for all curves y = hvsr_data['hvsr_curve'] #Calculated based on "Use" column - index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + + scorelist = ['score', 'scored', 'best', 's'] + maxlist = ['max', 'highest', 'm'] + # Convert peak_selection to numeric, get index of nearest value as list item for __init_peaks() + try: + peak_val = float(peak_selection) + index_list = [np.argmin(np.abs(x - peak_val))] + except: + # If score method is being used, get index list for __init_peaks() + if peak_selection in scorelist: + index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + elif peak_selection in maxlist: + #Get max index as item in list for __init_peaks() + startInd = np.argmin(np.abs(x - peak_freq_range[0])) + endInd = np.argmin(np.abs(x - peak_freq_range[1])) + if startInd > endInd: + holder = startInd + startInd = endInd + endInd = holder + subArrayMax = np.argmax(y[startInd:endInd]) + + # If max val is in subarray, this will be the same as the max of curve + # Otherwise, it will be the index of the value that is max within peak_freq_range + index_list = [subArrayMax+startInd] + hvsrp = hvsr_data['hvsrp'] #Calculated based on "Use" column hvsrm = hvsr_data['hvsrm'] #Calculated based on "Use" column @@ -789,7 +824,7 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose peakp = __init_peaks(x, hvsrp, index_p, hvsr_band, peak_freq_range) peakp = __check_clarity(x, hvsrp, peakp, do_rank=True) - #Do for hvsrm + # Do for hvsrm # Find the relative extrema of hvsrm (hvsr - 1 standard deviation) if not np.isnan(np.sum(hvsrm)): index_m = __find_peaks(hvsrm) @@ -799,6 +834,7 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose peakm = __init_peaks(x, hvsrm, index_m, hvsr_band, peak_freq_range) peakm = __check_clarity(x, hvsrm, peakm, do_rank=True) + # Get standard deviation of time peaks stdf = __get_stdf(x, index_list, hvsrPeaks) peak = __check_freq_stability(peak, peakm, peakp) @@ -1228,7 +1264,8 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr if plot_input_stream: try: params['InputPlot'] = _plot_specgram_stream(stream=dataIN, params=params, component='Z', stack_type='linear', detrend='mean', dbscale=True, fill_gaps=None, ylimstd=3, return_fig=True, fig=None, ax=None, show_plot=False) - _get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + #_get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + plt.show() except: print('Error with default plotting method, falling back to internal obspy plotting method') dataIN.plot(method='full', linewidth=0.25) @@ -6302,7 +6339,6 @@ def __check_freq_stability(_peak, _peakm, _peakp): return _peak - # Check stability def __check_stability(_stdf, _peak, _hvsr_log_std, rank): """Test peaks for satisfying stability conditions as outlined by SESAME 2004 From b30e4b07628b256ee11ba69d4eb65aadf77e354e Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:29:51 -0500 Subject: [PATCH 07/17] fixed print report --- sprit/__pycache__/sprit_gui.cpython-310.pyc | Bin 79800 -> 79798 bytes sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 176424 -> 176966 bytes sprit/sprit_gui.py | 4 +- sprit/sprit_hvsr.py | 158 +++++++++---------- 4 files changed, 75 insertions(+), 87 deletions(-) diff --git a/sprit/__pycache__/sprit_gui.cpython-310.pyc b/sprit/__pycache__/sprit_gui.cpython-310.pyc index adb14eb334c41ec6569465362fa5451c9affe0f3..f4ec6f43b22261c06a39922e53b6f4ea3519cb22 100644 GIT binary patch delta 66 zcmdn-o@Lv67T$bbUM>b82sN@v{j-sGL#v8#Kx$%kKv8~fW?pJua;j5mPJa0;M1& delta 68 zcmdn?o@K{-7T$bbUM>b8XvjBC{kM^KL#vudKx$&PTTyDEb53GWW=W+}YEFLnEp*Y% J4_mKp1OQEW8v6hM diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 3cda950bc5a03b2d10cf66bc52f74c1c0cc9e316..197cac973b775763f798abe332dfd7b32b30e2b3 100644 GIT binary patch delta 15311 zcmc(G33yaRws4>7HJyd*dqNXJl7;{Y1O$x88bBayfk0Sd$nAuXy}H@j8yk!WgWxbW zSV#mDK?Okpqpw8~!QDZD=Q3(k&{=#sE`!c<#=+6~&#BuZn;GFl?n!%p0 z)HEd{L8sz}SYN2=O5An2ru6O5_`aH<+9atTd*J-24v;tlBu=`LSA+fi0Mo7dM?)j8_Q9TOZi^=mq=DD;k6brDkA<5x$3k*`IwwcFP$0QlIue(kv2 zTf#OOzycN)%|aOcT7cn~2qsKh!3yy#9-%@0MS|7f{rlRouprAnB7sr4!So|*U$SnJ z5jK0$Xh+G71R+Yas4Hb=Jg^fV+QbT;;zHy4`>lf$ROWoR|F>6#+z445)gO6w>! zQg+PnUWCcR);eXxMn-g%k=?rNI$4wM@mEpR81%6G09twt&1 z<8*3toUSQqJ8N8}8n!d%9j*{Hsu>-0GgG5fJr0Hm+w|zJQQd4ZF<;B$?`Hix8ZeV^ z!07o&#jNORLYrbLb;{%ipec5i+wan=UE!ol%U49LZDDhNRVR!g?PMM zO@UUb#wj7FTqwpzEfwCCitI{7ccm}{yG)2lV=@W9lx9|Gs)VLQHBm96fjnVqy!8H< zm3`DiHCCCjjWs2yMwEZAD@2(}x+~L~k}-Xg60A($#!&mDm1MJwrW99%D^g8qhGtNc zF{JBxhL%@!YO<0NuOl~6W}rn=(fcH3Qz}|C6+1M!4Upn=4-wq1ZwzyVG-Hr;MY$q~ zJP}k(ak^-X%*ZOSsju2sD~Z)9W@+99DALSMk&wbf%okpfpGo5P6umXN4b8qZx5=j3 z6ib4KGQDZC<(F#WSA=Vdpg=Y5AoQA~fY%i3>MYtEi6YG+(H7}EYEm~>nvJeRdHnUe z(j2MlXResddKHwp$~>iLo1v*667EO+7a*?oGjxxw^NE6j5LqpdR)}qlqv?$3+KUVM zdl=w9FW0r-^=SndKc}$o{ont&F+IgL!%G-S)yF1Y~uePkF#x`~60^2;-B*$u7{b~odxvFX%-bX$g z>WR0}lxeGS5>YvAx@^m7+PrMU_Ji#1-Vn-`Basd^KF1x_)x4&RS33AO(CK(S0(W1n zrh+>fhY)$Et896VBf$MKn?0CMSVX8sTUniA!-7E_Y+i-ihJuzYud1nXt&_@--^&y<}+;8q0Ny`|qcjUL?w zcDwi8Zze-@d&;-F!{G4Fr{HEdE)oC=VYgTT&>xQbwgQZR=-o)ABxIG-QR-Z^yrHJv z<>aTuI}8TG^Y`dsqv;vqa#-A{hof-Wmv4ak_0S?dG(!?}_`Wy8reye7tWSrD@S*R; zbl3y%zF3|CHvki@8IWKy=&`*~JeL8t!T*X0nXna(`Oam+Lss~kSUn8#)4oLx2N{2t z1PQ7sL$7h7MR_}!qlGjxi?@eC7Srj)Ps1P0f+vk-%!yyVQ|76 z?4Sis)V12z=7bJ2%oge`kOI@i?pt8!lv+Z}A<#hJx1H+nWD=y2EIx=*L#Z~KQa&mh zLa7V_Z&QoaB3wl$xm8@R!ba5iv5AGsTFv?c`Q+k+yP6# zD!e;jN7V;J;|TEx88&>(h|&1pkkO+@b4I*Bq{?tgeMG4UN?oATGy)e1NXPt`Qbwwc zB=8AkZz6znH4XUh#rB=hns^VE7V@14-OL)qJtX{i0=vXb_rN50Mg006IDmaTO28;U z6&iOzHan*G9xG3RUsG<7_;eRq4151xSZMwWad}*;-YK@;3-6-eD!U;&crVpRC)+1l zcB3OdCT?f}Pxw)ikWpm=fk(ybEszrP2&E4pD6z=Mh|wJf%ug+F9>?do``~2=eLNK7 z5@P5=fFJiguotBTpYMO}hiw2a_*S*SCNQ>R{docVU?{Zva`%A~jJuKFXX4;~uw%@4 zYd?II@)9Eborr}YF$UpaFq-~pqx}lwFZn)t5EL*uuoZ-fDGx(BID9n^!#M^KzWhhv zlnM0W$D^>g&)rC-!#t5Y%3KbGdqw3jxL{e0?d*&{CuTklufTmGrX2>GWDiD&sqHWl zo)VsR97Q92ue8G-O}0~HjoC5xw)j<1MM zPQb*p^MvyTfwKe-BVviE!9AnO>EZ(k@0=)j7Dl5Y>z@Sy_xa*a!e$1eMcXMHI(py9 zQ_x_AC&kb+DBf#g{uvkqLToq#w?gKBy$)3@@?E0)1WHj*#2bj>3~|d@?6G2T=q&Vw zi{i{#Xo0=H%5!kY41W+&@4}<-M{)XH7!~sbsUelzPih2F^Cv~vdl(Df5eMJHSa(A)19c8tp{)Gzvnrtx!1?Vu=Io8Z`@zGT9keKi%$iR?T^(UAILB41I z1iv$4#Mto#q{sZ3Fg_+pA19fgCa_t&@db?O7i2&o;-zf^k(b{?4Ng)6tH}BiR>B40 z{Sx}Y0rBLQXzzzKW!ezSC%*a;)ob%z{}t>tU^wge22HR}g#Q4UnUZ*{MqCE?o!Fuy zh&yU=`7^Cf;Vr~RFN(i~IT;}+3GYMTLN}~=j`Ge1^NS|(1LEwrkZODYF}@Sud<(PS z0pIw)LcTdOidv$?xBY~Ghr6RCeBm_(^otQaFM{Q!%h)i>jxa5td*7k`9!!x`i%zWM?Zlcl6l znxuhwQu2C8p@smPhs?$3Cq5jX3fXOoM&C zfBb~gJsjWp8cTzionNvQo;$GGZsg@eY(J*ExzmG`-9q(-ZuMOr!M9RX_oi1E``1@C z?NYJBT3hC##aoSd3)s;9FVnaPsdTtX%U4v@C>-a6he)8;sek+{rU8tNfw#rYj3rE# z9ZKSrgedi;4dt#PZLQHITS+jM*VR`!9mQ33a)DA3ge7arc>aqii3LIR~XE!1a1?hW>##k0TLAIz2Vb=u*XoiW$LRew~3C>TDa?cW=(a^yf%J|pf!w{Bf z9FNtN!V-!U#*2ZW?C7ld)Bsh|@dcDBBp``gDHv6B5$-U|n2giJOe&l}U^0S(!1IJX zjHSk`iNtA~PJ2TPu@HjZYxoym;#(5Nnpn(nw0BX5(Y1;%ukUo#-Qw$Tc1JJ}B}O1y zcp}*38FK8uN{SGDjnlP+>#$Z-RhE|5)H`w7C%cpc*EZCYme<$SIm%spi3pEmgV1jI zku1%)7d5_3lt;43uvZ+6WI4Gv6NAMBCXz8Em_(^EEIUd1;IGcW3?NH*w$B>H)|!xD za{}Al?=~dJ$@Ta!q7hBmS(vg9*T%*hVr(MIOq2@I9Lt|1@FA)82Li#ulgKjCWw7kI z58$s5)kg?PE}`!euO+hVhq0Oc zI)03(cXckGL`b7VaT1#ggT*6BEH79JE(7rG;`1anKl~%&Do8XL*&L!UndMH91M&qb z4Yr2fT}UPxdOfr+G@1d}K=6w6-p{VSR2 z@$$f@Ph}U(xbFG=0G1BBggJwq$C3D61}nzS$;o6fadHfjt#O=7<3CKo?-ENg+1g+; zrKJugaWRvnUMI)iQ&egp-ev;Y^iO4@rE(-i4OL8{)csh|Ao8+U3*7HJpT)j6=FcSD zCy1rl9h%=zQ&#R+QD37t_*g!fstO29AuyG|Gy*pvuQ$L6qH zaEqxqYzBNScIL35@R@iihi!tjzL~k~4=lWxT8EG%izqc$jLTzo+>ouzW25brR7Bgb z4ogi{owETY(N;ooI-X8FzlTuj#Orx1KF&$$22|1>!`D%IErF%thdh=6w}`Y6>~5nA zi*6FfN1%mWzKD^`XD}@ztjXf+7`6zUBI|lq92{`twPMTlY^qI3I^+qi;_>8)*!7k+qu$+$t`N$M~h*GlAtBJy`OL7(W5y zq{mk|fz36csh*z7QecC4Zz@ZUrFNf0vXHq2y+ zuw2|XljR1>-E9?>SBm#$va}R8lJC$v);91uEVV~yw%v-Qe49b!6tWnRUC5^6R(EY7 z8)968NQ1u9LHxnPB|7!s!?9tBb?Sn7b0@Cis~x3oHrUCxiLJ#fDTnYlQRce|5GtpLR`n^- zF-SUE{b}MmT)bP%mPSbGLFiNtr)yn}gYOoT7qWh1T1X(%@_Lu6zE%#b4ntjiorA9@ zCUzu^1ZU$K_+b&tn?<`2-Ukhd#*=#!X%A_0 z;P<2MZsUaI^=q>}A4bNWK*snNvf??E#YsZPFB5p4*qiyA;?@#2Huw#~uo13Tyi&pj zjm;%4!wBRN7=oZ9$cgJISCtFxD9=nNZkJff>gwuUxRKR%R4a(a7?HD>O@bJ)YB5_h zgbvvFX##=p{U&AKB0%Atk0bCKQvGi6&&6z7Um4rv*;gqMlq=?&DOE<`YoRP*Q^6=6 zU4j$$K5>2t%Mq6%Ak6{dd{AsI zWhqHVFr60bA7mEGFxuqP24ByQ_)e9w69$i$uul<%9hfRHu5+zxa7Z8Lf~s~A*h=*; zl7h=9yOh8Q(h|!t{E<1q&80U)5Z2$RIF?K;dn^u9b||qdU*|;k!yQsQRV^fyn6VnF?%n^GXEX6~f&24_twPg(rbdt>P#ZvoV z{~CZ~nMw3W%Z~8QRdv1JD6x{Ot#jc@*QpKl!z7NWs@A!peocvK zg&fiR5pke`rFx`?lC4Xmhib7zdaT9isI980uUyBaKL`9EO^fdV7YI1yI8y0J62i1j zBNgaN*@IamY#y<*G&q&1GsNUdmSnntO2>+dO18y#4lDD-ca`iAoD*#;Sd>XxexUE^ z6>M~$;3~4=N&?m5r#0-xTv~7P3@XSZkcFU#pQOU)5Mca>A>yZ~K=US1x0cQD{7qJN z7*^wT1HrPj(hKZHie|i4!#z$EpC<4Wfu1g=xm5yDm%gRB7G?2}%fDHXo}Nl&6m(>C zY>_=^VgqKFC{sg3-Nsp7Rm`-Ij@F2`4p5*u?LAXc48yh-Fe~t=e zFq3X@nz93^O_y(S*{K1il@2LgQ#$EjLZ&AUG7$2|D0LHwBqz}vs>R`ugVPQrAEcUx z2s})H+_S^N%V(GIvRY?}zOuZ+zq}PwZ(*bGSaicJEIz!Rq`D+YQL0fKyoKf3Wq)5F ze$P|s3j|&!Abm#8uF@he3cbpvmA*n%uOcXkP#on|IDD(>>q;xi%4xifAmSqlTu0zn zM0FEMIEJz^+Fnnod;*$3Q|dLMIgV1}#UYhtC zv>22aEsIsLE^BKrUBK@#tDu_ja+vO=PEL!K+Dp}@(y7DP%1hPu)VrA(@r7v=_X%sxl z&mo}+as>&sGjBs1^+Um|eioEZBSE|6`{(N#hp2#eyXj>o&^d~5N$0e=>V2c{V9yym z!Q?OU(2gco&Gjfi@|UQBR?J0mMOvs$wKPTYVd!)P+Tje{WVrRVu{O6pXUK53*_M@? zV;hP&yUnJ#>1HJ6wjjo^oE$9s6=qk_8zQuvcqd11o0 zh~0Ox;l+XNhFm|%wUWF`qKjsJA4$6hfjv^2oz9ZvG|_NUuEeUH{Fc@@>blZ(t7`F7 zJ$);i20l@@mDvj4h{FL=i1eaU!&x+L5hC#cR*2jv&?78^-T*earm^@TTW`?Q8V4U} ztv9f7FqRo`lbXg%IPk`a3tL&=V44i%z)Lp>%QiO6bE3)UGPRo9=qa2L`^uQFQ%wFe z-q|Y#92ge#>!5CBR)1M=x3Un#3Dqd!h_U!*^u`zxiB{G>YVL1oF^$|#?rkwo3C z{+dqRQ~i0r?kF}3(lsiL{Z&2x)Z>z}m7z&o29?q46tu{doq}WZ-*yUhQuhwA;^-&A zdhZmQzf(Na=Oh`9St$aUAL0?B7;CTYnCR{uW5nPyv#Sea*U7h%56aG2&{cC)=U}@B zHLG}(9{tm;1Jb$-iqoNVO|n9NKL%-=?(ZT>5Afr3b`h0jbW!N^F6t7}JkTZhI1e?) zKG6r!d&{AXU#fbP?r3MXD6viE9$iz6-0T#sRK#OmeoM- zSjy;uiksPOMf?q-w3Wi-7+OT!jwyF&pnaLk<~AbxF*bglvkZumN8 zm4eS{@G+2lALB+osMqy0A4Ciw#W0J%xl%xJ!%o)k1{ud?%%*Uy-GR#JO~Jg!0!D`L z;aFN?T8Xb1xC^6o%T8unmKKj;x&-5@A;|*KEDQs5xIP>YGsE$(2m^OAERX@(z$h$J zu-0m$Azz=0_c%5vko6|4HyP*@C>&y09@a!dhDf*vWB3*^_8#0GJ*{CStJCG_Gd+$j=~;w!whhgS^-vFzRoIr53*}E!VVVsNa^IQTe2_C6i0R-fj7v z-9VB1ke|H_7a?qzZ1Z*}WTd=%Q*m#E_N=(A{NB|-+%=YKf^ zGzBWyc-*()UKXu4$?eEpqV+zO=;3c-u#gKp3SDceTq~;TY|kGXF@Qn^e;X@{v~7>v z$jCiRK7}9rboL(BmWgqd>rZj5mzJ)tpHmiMj3=_!0zz~Q8tRTBjc z9JhEPr}#_#E55Ee#VW@uh8;PeOgQvt-9T@98G4mT36+biF#jT}50>CdUnK&!7Wnd0 z;}oFj(HM3SHTFhSi3|`?qMH0uu4+Lsk1EmVA0z;Y6{K1u>0noH;^})4ud-9V7<8Dh zUq-A;EUm3kvz~vM`T;IHaTURDljHS4vHgA-YIyY3Hn_qB{}H=hK~poU=ZeniQUE(g ziR*4x6kPdb#<*t3cQfq5pW97cf@zIoyIU4T+pff;Z5h8#v#pXKPPVc*&ujl5O^cTG zbRP#@8jxuddzhB-g#Wy0(XyVOH3GT>(@Y!R!?cV~R+6d#P5Y(aw8^~%2duyKYFl49 zl>hJixOX?kpfjZmD*uZ3r(}9 zaQb$~&2tUG2mV87rk_ol0`1LwmRu6>cL?t-0<_c6&W7X~fZhleX{VVtAen;7r&2e5 zG3TiZH@ai5BuKlQWwtCnmR1b%C@K|Gl}G^pUAmFW3qm^k=W?4QZx5nqrC`z)7J)Uz z6(>hQCB{;xOR3;zMa==c4L>Km2iPFb3$$!FfV2&X2HYz|(ryBm3L&@zh{l(t;SdHR z@sP-X-w>G_f;*B_yibemQ5)8+*7gf(2NCJ+jl-_p*mwIuwi@tK?}tZNs-*$^W*c%=oL6`iXFVE51F#M)OZ3*2+#{A?IjYuhLKND_*zQ2sG^Yoy^X-P4hZOR1D``O(8B_L wmN?U0ET_vf{#Ou-kFdWbjIr8v9{RIJNNBV!mcKP(Hq)$ delta 14702 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 GVfue7MtW=j diff --git a/sprit/sprit_gui.py b/sprit/sprit_gui.py index 8458ebc..3f6135d 100644 --- a/sprit/sprit_gui.py +++ b/sprit/sprit_gui.py @@ -533,8 +533,8 @@ def report_results(hvsr_results): self.peakTest6ResultText.configure(text=hvsr_results['BestPeak']['Report']['Sa'][:-1]) self.peakTest6Result.configure(text=hvsr_results['BestPeak']['Report']['Sa'][-1]) - peakPass = (hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakPass = (hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index c3f267e..fdf19e6 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -794,7 +794,7 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_ startInd = endInd endInd = holder subArrayMax = np.argmax(y[startInd:endInd]) - + # If max val is in subarray, this will be the same as the max of curve # Otherwise, it will be the index of the value that is max within peak_freq_range index_list = [subArrayMax+startInd] @@ -846,7 +846,7 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_ # Get the BestPeak based on the peak score # Calculate whether each peak passes enough tests curveTests = ['WindowLengthFreq.','SignificantCycles', 'LowCurveStDevOverTime'] - peakTests = ['PeakFreqClarityBelow', 'PeakFreqClarityAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] + peakTests = ['PeakProminenceBelow', 'PeakProminenceAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] bestPeakScore = 0 for p in hvsr_data['PeakReport']: @@ -880,6 +880,10 @@ def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_ else: hvsr_data['BestPeak'] = {} print(f"Processing Errors: No Best Peak identified for {hvsr_data['site']}") + try: + hvsr_data.plot() + except: + pass return hvsr_data @@ -1788,8 +1792,8 @@ def get_report(hvsr_results, report_format='print', plot_type='HVSR p ann C+ p a curvePass = curvTestsPassed > 2 #Peak Pass? - peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ @@ -1924,24 +1928,25 @@ def report_output(_report_format, _plot_type='HVSR p ann C+ p ann Spec', _return report_string_list.append(internalSeparator) report_string_list.append('') + justSize=34 #Print individual results report_string_list.append('\tCurve Tests: {}/3 passed (3/3 needed)'.format(curvTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]} Length of processing windows: {hvsr_results['BestPeak']['Report']['Lw']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]} Number of significant cycles: {hvsr_results['BestPeak']['Report']['Nc']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]} Low StDev. of H/V Curve over time: {hvsr_results['BestPeak']['Report']['σ_A(f)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]}"+" Length of processing windows".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Lw']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]}"+" Number of significant cycles".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Nc']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]}"+" Small H/V StDev over time".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['σ_A(f)']}") report_string_list.append('') report_string_list.append("\tPeak Tests: {}/6 passed (5/6 needed)".format(peakTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]} Clarity Below Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f-)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]} Clarity Above Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f+)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]} Clarity of Peak Amplitude: {hvsr_results['BestPeak']['Report']['A0']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]}"+" Peak is prominent below".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f-)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]}"+" Peak is prominent above".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f+)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]}"+" Peak is large".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A0']}") if hvsr_results['BestPeak']['PassList']['FreqStability']: res = sprit_utils.check_mark() else: res = sprit_utils.x_mark() - report_string_list.append(f"\t\t {res} Stability of Peak Freq. Over time: {hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]} Stability of Peak (Freq. StDev): {hvsr_results['BestPeak']['Report']['Sf']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]} Stability of Peak (Amp. StDev): {hvsr_results['BestPeak']['Report']['Sa']}") + report_string_list.append(f"\t\t {res}"+ " Peak freq. is stable over time".ljust(justSize)+ f"{hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]}"+" Stability of peak (Freq. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sf']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]}"+" Stability of peak (Amp. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sa']}") report_string_list.append('') report_string_list.append(f"Calculated using {hvsr_results['hvsr_df']['Use'].sum()}/{hvsr_results['hvsr_df']['Use'].count()} time windows".rjust(sepLen-1)) report_string_list.append(extSiteSeparator) @@ -1965,7 +1970,7 @@ def report_output(_report_format, _plot_type='HVSR p ann C+ p ann Spec', _return import pandas as pd pdCols = ['Site Name', 'Acq_Date', 'Longitude', 'Latitide', 'Elevation', 'PeakFrequency', 'WindowLengthFreq.','SignificantCycles','LowCurveStDevOverTime', - 'PeakFreqClarityBelow','PeakFreqClarityAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] + 'PeakProminenceBelow','PeakProminenceAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] d = hvsr_results criteriaList = [] for p in hvsr_results['BestPeak']["PassList"]: @@ -6122,6 +6127,7 @@ def __check_curve_reliability(hvsr_data, _peak): window_num = np.array(hvsr_data['psd_raw'][anyKey]).shape[0] for _i in range(len(_peak)): + # Test 1 peakFreq= _peak[_i]['f0'] test1 = peakFreq > 10/window_len @@ -6131,38 +6137,37 @@ def __check_curve_reliability(hvsr_data, _peak): halfF0 = peakFreq/2 doublef0 = peakFreq*2 + test3 = True failCount = 0 for i, freq in enumerate(hvsr_data['x_freqs'][anyKey][:-1]): - ###IS THIS RIGHT??? if freq >= halfF0 and freq = 0.5: - if hvsr_data['hvsr_log_std'][i] >= 2: + if hvsr_data['hvsr_log_std'][i] >= compVal: test3=False failCount +=1 + else: #if peak freq is less than 0.5 - if hvsr_data['hvsr_log_std'][i] >= 3: + compVal = 3 + if hvsr_data['hvsr_log_std'][i] >= compVal: test3=False failCount +=1 if test1: - _peak[_i]['Report']['Lw'] = '{} > 10 / {} {}'.format(round(peakFreq,3), int(window_len), sprit_utils.check_mark()) + _peak[_i]['Report']['Lw'] = f'{round(peakFreq,3)} > {10/int(window_len):0.3} (10 / {int(window_len)}) {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['Lw'] = '{} > 10 / {} {}'.format(round(peakFreq,3), int(window_len), '✘') + _peak[_i]['Report']['Lw'] = f'{round(peakFreq,3)} > {10/int(window_len):0.3} (10 / {int(window_len)}) {sprit_utils.x_mark()}' if test2: - _peak[_i]['Report']['Nc'] = '{} > 200 {}'.format(round(nc,0), sprit_utils.check_mark()) + _peak[_i]['Report']['Nc'] = f'{int(nc)} > 200 {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['Nc'] = '{} > 200 {}'.format(round(nc,0), '✘') + _peak[_i]['Report']['Nc'] = f'{int(nc)} > 200 {sprit_utils.x_mark()}' if test3: - if peakFreq >= 0.5: - compVal = 2 - else: - compVal = 3 - _peak[_i]['Report']['σ_A(f)'] = 'σ_A for all freqs {}-{} < {} {}'.format(round(peakFreq*0.5, 3), round(peakFreq*2, 3), compVal, sprit_utils.check_mark()) + _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['σ_A(f)'] = 'σ_A for all freqs {}-{} < {} {}'.format(round(peakFreq*0.5, 3), round(peakFreq*2, 3), compVal, '✘') + _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.x_mark()}' _peak[_i]['PassList']['WindowLengthFreq.'] = test1 _peak[_i]['PassList']['SignificantCycles'] = test2 @@ -6208,16 +6213,16 @@ def __check_clarity(_x, _y, _peak, do_rank=True): for _i in range(len(_peak)): #Initialize as False - _peak[_i]['f-'] = '✘' - _peak[_i]['Report']['A(f-)'] = 'No A_h/v in freqs {}-{} < {} {}'.format(round(_peak[_i]['A0']/4, 3), round(_peak[_i]['A0'], 3), round(_peak[_i]['A0']/2, 3), '✘') - _peak[_i]['PassList']['PeakFreqClarityBelow'] = False #Start with assumption that it is False until we find an instance where it is True + _peak[_i]['f-'] = sprit_utils.x_mark() + _peak[_i]['Report']['A(f-)'] = f"H/V curve > {_peak[_i]['A0']/2:0.2f} for all {_peak[_i]['A0']/4:0.2f} Hz-{_peak[_i]['A0']:0.3f} Hz {sprit_utils.x_mark()}" + _peak[_i]['PassList']['PeakProminenceBelow'] = False #Start with assumption that it is False until we find an instance where it is True for _j in range(jstart, -1, -1): # There exist one frequency f-, lying between f0/4 and f0, such that A0 / A(f-) > 2. if (float(_peak[_i]['f0']) / 4.0 <= _x[_j] < float(_peak[_i]['f0'])) and float(_peak[_i]['A0']) / _y[_j] > 2.0: _peak[_i]['Score'] += 1 _peak[_i]['f-'] = '%10.3f %1s' % (_x[_j], sprit_utils.check_mark()) - _peak[_i]['Report']['A(f-)'] = 'A({}): {} < {} {}'.format(round(_x[_j], 3), round(_y[_j], 3), round(_peak[_i]['A0']/2,3), sprit_utils.check_mark()) - _peak[_i]['PassList']['PeakFreqClarityBelow'] = True + _peak[_i]['Report']['A(f-)'] = f"Amp. of H/V Curve @{_x[_j]:0.3f}Hz ({_y[_j]:0.3f}) < {_peak[_i]['A0']/2:0.3f} (peak amp. {_peak[_i]['A0']:0.3f}) {sprit_utils.check_mark()}" + _peak[_i]['PassList']['PeakProminenceBelow'] = True break else: pass @@ -6226,25 +6231,21 @@ def __check_clarity(_x, _y, _peak, do_rank=True): max_rank += 1 for _i in range(len(_peak)): #Initialize as False - _peak[_i]['f+'] = '✘' - _peak[_i]['Report']['A(f+)'] = 'No A_h/v in freqs {}-{} < {} {}'.format(round(_peak[_i]['A0'], 3), round(_peak[_i]['A0']*4, 3), round(_peak[_i]['A0']/2, 3), '✘') - _peak[_i]['PassList']['PeakFreqClarityAbove'] = False + _peak[_i]['f+'] = sprit_utils.x_mark() + _peak[_i]['Report']['A(f+)'] = f"H/V curve > {_peak[_i]['A0']/2:0.2f} for all {_peak[_i]['A0']:0.2f} Hz-{_peak[_i]['A0']*4:0.3f} Hz {sprit_utils.x_mark()}" + _peak[_i]['PassList']['PeakProminenceAbove'] = False for _j in range(len(_x) - 1): # There exist one frequency f+, lying between f0 and 4*f0, such that A0 / A(f+) > 2. if float(_peak[_i]['f0']) * 4.0 >= _x[_j] > float(_peak[_i]['f0']) and \ float(_peak[_i]['A0']) / _y[_j] > 2.0: _peak[_i]['Score'] += 1 - _peak[_i]['f+'] = '%10.3f %1s' % (_x[_j], sprit_utils.check_mark()) - _peak[_i]['Report']['A(f+)'] = 'A({}): {} < {} {}'.format(round(_x[_j], 3), round(_y[_j], 3), round(_peak[_i]['A0']/2,3), sprit_utils.check_mark()) - _peak[_i]['PassList']['PeakFreqClarityAbove'] = True + _peak[_i]['f+'] = f"{_x[_j]:0.3f} {sprit_utils.check_mark()}" + _peak[_i]['Report']['A(f+)'] = f"H/V Curve at {_x[_j]:0.2f} Hz: {_y[_j]:0.2f} < {_peak[_i]['A0']/2:0.2f} (f0/2) {sprit_utils.check_mark()}" + _peak[_i]['PassList']['PeakProminenceAbove'] = True break else: pass -# if False in clarityPass: -# _peak[_i]['PassList']['PeakFreqClarityBelow'] = False -# else: -# _peak[_i]['PassList']['PeakFreqClarityAbove'] = True #Amplitude Clarity test # Only peaks with A0 > 2 pass @@ -6254,11 +6255,11 @@ def __check_clarity(_x, _y, _peak, do_rank=True): for _i in range(len(_peak)): if float(_peak[_i]['A0']) > _a0: - _peak[_i]['Report']['A0'] = '%10.2f > %0.1f %1s' % (_peak[_i]['A0'], _a0, sprit_utils.check_mark()) + _peak[_i]['Report']['A0'] = f"Amplitude of peak ({_peak[_i]['A0']:0.2f}) > {int(_a0)} {sprit_utils.check_mark()}" _peak[_i]['Score'] += 1 _peak[_i]['PassList']['PeakAmpClarity'] = True else: - _peak[_i]['Report']['A0'] = '%10.2f > %0.1f %1s' % (_peak[_i]['A0'], _a0, '✘') + _peak[_i]['Report']['A0'] = '%0.2f > %0.1f %1s' % (_peak[_i]['A0'], _a0, sprit_utils.x_mark()) _peak[_i]['PassList']['PeakAmpClarity'] = False return _peak @@ -6295,19 +6296,17 @@ def __check_freq_stability(_peak, _peakm, _peakp): for _i in range(len(_peak)): _dx = 1000000. _found_m.append(False) - _peak[_i]['Report']['P-'] = '✘' + _peak[_i]['Report']['P-'] = sprit_utils.x_mark() for _j in range(len(_peakm)): if abs(_peakm[_j]['f0'] - _peak[_i]['f0']) < _dx: _index = _j _dx = abs(_peakm[_j]['f0'] - _peak[_i]['f0']) if _peak[_i]['f0'] * 0.95 <= _peakm[_j]['f0'] <= _peak[_i]['f0'] * 1.05: - _peak[_i]['Report']['P-'] = '%0.3f within ±5%s of %0.3f %1s' % (_peakm[_j]['f0'], '%', - _peak[_i]['f0'], sprit_utils.check_mark()) + _peak[_i]['Report']['P-'] = f"{_peakm[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.check_mark()}" _found_m[_i] = True break - if _peak[_i]['Report']['P-'] == '✘': - _peak[_i]['Report']['P-'] = '%0.3f within ±5%s of %0.3f %1s' % (_peakm[_j]['f0'], '%', ##changed i to j - _peak[_i]['f0'], '✘') + if _peak[_i]['Report']['P-'] == sprit_utils.x_mark(): + _peak[_i]['Report']['P-'] = f"{_peakm[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" # Then Check above _found_p = list() @@ -6321,21 +6320,18 @@ def __check_freq_stability(_peak, _peakm, _peakp): _dx = abs(_peakp[_j]['f0'] - _peak[_i]['f0']) if _peak[_i]['f0'] * 0.95 <= _peakp[_j]['f0'] <= _peak[_i]['f0'] * 1.05: if _found_m[_i]: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % ( - _peakp[_j]['f0'], '%', _peak[_i]['f0'], sprit_utils.check_mark()) + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.check_mark()}" _peak[_i]['Score'] += 1 _peak[_i]['PassList']['FreqStability'] = True else: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % ( - _peakp[_j]['f0'], '%', _peak[_i]['f0'], '✘') + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" _peak[_i]['PassList']['FreqStability'] = False break else: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % (_peakp[_j]['f0'], '%', _peak[_i]['f0'], '✘') + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" _peak[_i]['PassList']['FreqStability'] = False if _peak[_i]['Report']['P+'] == sprit_utils.x_mark() and len(_peakp) > 0: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % ( - _peakp[_j]['f0'], '%', _peak[_i]['f0'], sprit_utils.x_mark()) #changed i to j + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" return _peak @@ -6379,104 +6375,96 @@ def __check_stability(_stdf, _peak, _hvsr_log_std, rank): if _this_peak['f0'] < 0.2: _e = 0.25 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True - else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.48 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, - sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif 0.2 <= _this_peak['f0'] < 0.5: _e = 0.2 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.40 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, - sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif 0.5 <= _this_peak['f0'] < 1.0: _e = 0.15 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.3 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif 1.0 <= _this_peak['f0'] <= 2.0: _e = 0.1 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s ' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.25 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif _this_peak['f0'] > 0.2: _e = 0.05 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.2 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False return _peak From f9094d1e3b2bf9ad3e3c5451d228bf95623ea53d Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Sat, 28 Oct 2023 03:28:44 -0500 Subject: [PATCH 08/17] plot update --- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 176966 -> 177177 bytes sprit/sprit_hvsr.py | 100 +++++++++++-------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 197cac973b775763f798abe332dfd7b32b30e2b3..a750cdb6432e5eb5878fc1600ad5fe0ad04a68ef 100644 GIT binary patch delta 14282 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 delta 13894 zcmaia31E~((&$vrkx3>Ax$hfDG9)D7NFX6{hEohzxP~JP`6d@hCh7T*B<~wX1w5m{wW5e`b)UNOSM`?@_kRyfO?OpwRdscDRUhA7 zUxvN>d03=7A|lLy|IYX-U9C?Xi~RO(u=xLd_gtT1-DdEHXdx=w3SN_D>M?4TeE{!q6~wJCF&44Um;xCw|_X;^9KPyt(mL5Uv1A5%HJ9#v^rjhz2Ko(@$r zEJM9YY+nk4Bl zTFd}F7U{7_Px43gq^BlJdb}1tKu^LfaUlh<)lhOgF(sM58hP# z&BEU_#pF$IFnBZkpZ6sCP@aPFwCt^n^!L}$ju>5=5!>F=`!eyc2SCZ*#C9@Xj}jAxNR{0~ zGj+9nI{IpmN@p5))PKh=jLO{2XP)Fd=MS5iLPX81#V|Zg7XEGQK~5srmWA~I&Q1cTc9L*3w0s1O3>Ho-m!XJ8#`3B zw@53}3X%-oVy##!N;P;(v=XgwlL>wDZ>_{zxRZ5!(l5s+1&#a9g0uo`WaOp8l_)vN z$ZJ@?PjuD&UK?(R-rf_x)7W9w3YAe(TD6vrGON|Jo^)lj>dQbvZk$2M+sbwul>EEp zsJ6vwMr3kIRwqVMyE>e2%-u!`sYR9B)Nl=PQYV7YYMSU2Olc|;JvsN2VlcP`O zAf*_cGhRXXJ4&9UAk-=7n5(o5ABvnPC$q7mNzH1`)-tpSO34o9Ez`=B0&HSZAlZ;; zz}mP_Ll2Xdh)b#>U(p( z5oRXo!uC5&9d;^_RNtUjWvV7q04`?}--Sx1O5x?+3SGF~M2f5+k^L%u zkDuP(DX9coMD%SzLb0W!;gDLTtG~j5)b|&-Ur>uRN}{N9nZZ8SS!d~;bm$Q7mL7Qy zYW>d~xeeC&mmYl;d+1L(c0b(T`~0zQz|3v9yYe6IeGtt4m5&Or`oDX$1suI=AA27l zySFTm%F-5s!4Q^VW%wTq{AaRS!yt_Hc06%?C`|SL{6fBE8Z#L9wBG!abC{)y%5(hg zm$Ku6#+mb~c@+w=TY{!GXRC`Z@jvj=KcLEg`^&RUOOVt5{>ygD25REyjX1Ripwi#; z%EM6FoA_#dY(fVTYWP=ZDEAU*A;#8T{|Au_QvLfs+zHLS6FzFSrq4q{H!IBIbEyy> zI=jVnxl?U&w^d~E`MuA4@hY=jL2WmoE!^k7tp&+*-lDpK+ysehIJeylo55LMk^82w+;$QN81swNp|9;f?tBCgz0!Ij3 zP2k_8?I@Lw5s=EO2zLd7oxPuaKLX&b-t*_@F!-at{Gb0AeUS2!X3`2ZyON-V;+8mZ zM~F=6*;=lWg!v8l3ICEGlN|peQ?g__qo(PCloTa(he;QqCSs6JQ#1DAY{EF5t%aeu zk2O3~WOpclb>tFQ#`A1)vnM4+i{JC{BZI;^Zh;2_K8p(J(9O0}@LUP)


;lxf%~binfy^|v5UadB>$wCUJBD} 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 Date: Sat, 28 Oct 2023 14:04:37 -0500 Subject: [PATCH 09/17] update plot and print reports --- conda/meta.yaml | 4 +- docs/generate_docs.py | 2 +- docs/main.html | 527 +++++-- docs/sprit_gui.html | 183 ++- docs/sprit_hvsr.html | 1323 +++++++++++++----- docs/sprit_utils.html | 28 +- pyproject.toml | 2 +- setup.py | 2 +- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 177177 -> 177566 bytes sprit/sprit_hvsr.py | 122 +- 10 files changed, 1597 insertions(+), 596 deletions(-) diff --git a/conda/meta.yaml b/conda/meta.yaml index 3cb9414..af2ce6b 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,10 +1,10 @@ package: name: sprit - version: 0.1.50 + version: 0.1.51 source: git_url: https://github.com/RJbalikian/SPRIT-HVSR - git_tag: v0.1.50 + git_tag: v0.1.51 build: number: 0 diff --git a/docs/generate_docs.py b/docs/generate_docs.py index 7383dae..5a34648 100644 --- a/docs/generate_docs.py +++ b/docs/generate_docs.py @@ -8,7 +8,7 @@ #Whether to convert_md using markdown library (True), or let github do it (False) convert_md=True rtd_theme=False #Not currently working -release_version= '0.1.50' +release_version= '0.1.51' run_tests=True lint_it=True diff --git a/docs/main.html b/docs/main.html index 677390b..5690b38 100644 --- a/docs/main.html +++ b/docs/main.html @@ -49,6 +49,7 @@

Package sprit

process_hvsr, plot_hvsr, remove_noise, + save_settings, check_peaks, get_report, HVSRData, @@ -92,6 +93,7 @@

Package sprit

'process_hvsr', 'plot_hvsr', 'remove_noise', + 'save_settings', 'check_peaks', 'get_report', 'HVSRData', @@ -516,7 +518,7 @@

Raises

Expand source code
def check_gui_requirements():
-    print("Checking requirements for gui")
+    #First, check requirements
     # Define a command that tries to open a window
     command = "python -c \"import tkinter; tkinter.Tk()\""
 
@@ -529,7 +531,7 @@ 

Raises

oktoproceed=True else: oktoproceed=False - print("GUI window could not be created.") + print("GUI window cannot be created.") return oktoproceed @@ -571,7 +573,7 @@

Raises

-def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose=False) +def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_range=[1, 20], verbose=False)

Function to run tests on HVSR peaks to find best one and see if it passes quality checks

@@ -581,6 +583,10 @@

Parameters

Dictionary containing all the calculated information about the HVSR data (i.e., hvsr_out returned from process_hvsr)
hvsr_band : tuple or list, default=[0.4, 40]
2-item tuple or list with lower and upper limit of frequencies to analyze
+
peak_selection : str or numeric, default='max'
+
How to select the "best" peak used in the analysis. For peak_selection="max" (default value), the highest peak within peak_freq_range is used. +For peak_selection='scored', an algorithm is used to select the peak based in part on which peak passes the most SESAME criteria. +If a numeric value is used (e.g., int or float), this should be a frequency value to manually select as the peak of interest.
peak_freq_range : tuple or list, default=[1, 20];
The frequency range within which to check for peaks. If there is an HVSR curve with multiple peaks, this allows the full range of data to be processed while limiting peak picks to likely range.
verbose : bool, default=False
@@ -596,7 +602,7 @@

Returns

Expand source code -
def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose=False):
+
def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_range=[1, 20], verbose=False):
     """Function to run tests on HVSR peaks to find best one and see if it passes quality checks
 
         Parameters
@@ -605,6 +611,10 @@ 

Returns

Dictionary containing all the calculated information about the HVSR data (i.e., hvsr_out returned from process_hvsr) hvsr_band : tuple or list, default=[0.4, 40] 2-item tuple or list with lower and upper limit of frequencies to analyze + peak_selection : str or numeric, default='max' + How to select the "best" peak used in the analysis. For peak_selection="max" (default value), the highest peak within peak_freq_range is used. + For peak_selection='scored', an algorithm is used to select the peak based in part on which peak passes the most SESAME criteria. + If a numeric value is used (e.g., int or float), this should be a frequency value to manually select as the peak of interest. peak_freq_range : tuple or list, default=[1, 20]; The frequency range within which to check for peaks. If there is an HVSR curve with multiple peaks, this allows the full range of data to be processed while limiting peak picks to likely range. verbose : bool, default=False @@ -617,6 +627,10 @@

Returns

""" orig_args = locals().copy() #Get the initial arguments + hvsr_data['processing_parameters']['check_peaks'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['check_peaks'][key] = value + if (verbose and 'input_params' not in hvsr_data.keys()) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: pass @@ -652,13 +666,38 @@

Returns

if hvsr_data['ProcessingStatus']['OverallStatus']: if not hvsr_band: hvsr_band = [0.4,40] + hvsr_data['hvsr_band'] = hvsr_band anyK = list(hvsr_data['x_freqs'].keys())[0] x = hvsr_data['x_freqs'][anyK] #Consistent for all curves y = hvsr_data['hvsr_curve'] #Calculated based on "Use" column - index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + + scorelist = ['score', 'scored', 'best', 's'] + maxlist = ['max', 'highest', 'm'] + # Convert peak_selection to numeric, get index of nearest value as list item for __init_peaks() + try: + peak_val = float(peak_selection) + index_list = [np.argmin(np.abs(x - peak_val))] + except: + # If score method is being used, get index list for __init_peaks() + if peak_selection in scorelist: + index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + elif peak_selection in maxlist: + #Get max index as item in list for __init_peaks() + startInd = np.argmin(np.abs(x - peak_freq_range[0])) + endInd = np.argmin(np.abs(x - peak_freq_range[1])) + if startInd > endInd: + holder = startInd + startInd = endInd + endInd = holder + subArrayMax = np.argmax(y[startInd:endInd]) + + # If max val is in subarray, this will be the same as the max of curve + # Otherwise, it will be the index of the value that is max within peak_freq_range + index_list = [subArrayMax+startInd] + hvsrp = hvsr_data['hvsrp'] #Calculated based on "Use" column hvsrm = hvsr_data['hvsrm'] #Calculated based on "Use" column @@ -684,7 +723,7 @@

Returns

peakp = __init_peaks(x, hvsrp, index_p, hvsr_band, peak_freq_range) peakp = __check_clarity(x, hvsrp, peakp, do_rank=True) - #Do for hvsrm + # Do for hvsrm # Find the relative extrema of hvsrm (hvsr - 1 standard deviation) if not np.isnan(np.sum(hvsrm)): index_m = __find_peaks(hvsrm) @@ -694,6 +733,7 @@

Returns

peakm = __init_peaks(x, hvsrm, index_m, hvsr_band, peak_freq_range) peakm = __check_clarity(x, hvsrm, peakm, do_rank=True) + # Get standard deviation of time peaks stdf = __get_stdf(x, index_list, hvsrPeaks) peak = __check_freq_stability(peak, peakm, peakp) @@ -705,7 +745,7 @@

Returns

# Get the BestPeak based on the peak score # Calculate whether each peak passes enough tests curveTests = ['WindowLengthFreq.','SignificantCycles', 'LowCurveStDevOverTime'] - peakTests = ['PeakFreqClarityBelow', 'PeakFreqClarityAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] + peakTests = ['PeakProminenceBelow', 'PeakProminenceAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] bestPeakScore = 0 for p in hvsr_data['PeakReport']: @@ -739,6 +779,10 @@

Returns

else: hvsr_data['BestPeak'] = {} print(f"Processing Errors: No Best Peak identified for {hvsr_data['site']}") + try: + hvsr_data.plot() + except: + pass return hvsr_data
@@ -962,7 +1006,9 @@

Parameters

date=params['acq_date'] #Cleanup for gui input - if '}' in str(params['datapath']): + if isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass + elif '}' in str(params['datapath']): params['datapath'] = params['datapath'].as_posix().replace('{','') params['datapath'] = params['datapath'].split('}') @@ -975,7 +1021,10 @@

Parameters

#Make sure datapath is pointing to an actual file if isinstance(params['datapath'],list): for i, d in enumerate(params['datapath']): - params['datapath'][i] = sprit_utils.checkifpath(str(d).strip()) + params['datapath'][i] = sprit_utils.checkifpath(str(d).strip(), sample_list=sampleList) + dPath = params['datapath'] + elif isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass else: dPath = sprit_utils.checkifpath(params['datapath'], sample_list=sampleList) @@ -1027,15 +1076,26 @@

Parameters

#Select which instrument we are reading from (requires different processes for each instrument) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'tromino 3g', 'tromino 3g+', 'tr', 't'] + + #Get any kwargs that are included in obspy.read + obspyReadKwargs = {} + for argName in inspect.getfullargspec(obspy.read)[0]: + if argName in kwargs.keys(): + obspyReadKwargs[argName] = kwargs[argName] #Select how reading will be done if source=='raw': - if inst.lower() in raspShakeInstNameList: - try: + try: + if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) - except: - raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") - return params + + elif inst.lower() in trominoNameList: + rawDataIN = __read_tromino_files(dPath, params, verbose=verbose) + except: + raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") + elif source=='stream' or isinstance(params, (obspy.Stream, obspy.Trace)): + rawDataIN = params['datapath'].copy() elif source=='dir': if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) @@ -1046,28 +1106,27 @@

Parameters

for f in temp_file_glob: currParams = params currParams['datapath'] = f + curr_data = fetch_data(params, source='file', #all the same as input, except just reading the one file using the source='file' - trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs), + trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs) + curr_data.merge() obspyFiles[f.stem] = curr_data #Add path object to dict, with filepath's stem as the site name return HVSRBatch(obspyFiles) - elif source=='file' and str(params['datapath']).lower() not in sampleList: if isinstance(dPath, list) or isinstance(dPath, tuple): rawStreams = [] for datafile in dPath: - rawStream = obspy.read(datafile) + rawStream = obspy.read(datafile, **obspyReadKwargs) rawStreams.append(rawStream) #These are actually streams, not traces - for i, stream in enumerate(rawStreams): if i == 0: rawDataIN = obspy.Stream(stream) #Just in case else: rawDataIN = rawDataIN + stream #This adds a stream/trace to the current stream object - elif str(dPath)[:6].lower()=='sample': pass else: - rawDataIN = obspy.read(dPath)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) + rawDataIN = obspy.read(dPath, **obspyReadKwargs)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) import warnings with warnings.catch_warnings(): warnings.simplefilter(action='ignore', category=UserWarning) @@ -1114,12 +1173,15 @@

Parameters

except: RuntimeError(f'source={source} not recognized, and datapath cannot be read using obspy.read()') + #Get metadata from the data itself, if not reading raw data try: dataIN = rawDataIN.copy() if source!='raw': #Use metadata from file for; # site if params['site'] == "HVSR Site": + if isinstance(dPath, (list, tuple)): + dPath = dPath[0] params['site'] = dPath.stem params['params']['site'] = dPath.stem @@ -1156,11 +1218,13 @@

Parameters

today_Starttime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=0, minute=0, second=0, microsecond=0)) - maxStarttime = datetime.time(hour=0, minute=0, second=0, microsecond=0) + maxStarttime = datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, day=params['acq_date'].day, + hour=0, minute=0, second=0, microsecond=0, tzinfo=datetime.timezone.utc) if str(params['starttime']) == str(today_Starttime): for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, - second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond) + currTime = datetime.datetime(year=tr.stats.starttime.year, month=tr.stats.starttime.month, day=tr.stats.starttime.day, + hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, + second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond, tzinfo=datetime.timezone.utc) if currTime > maxStarttime: maxStarttime = currTime @@ -1175,17 +1239,19 @@

Parameters

today_Endtime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=23, minute=59, second=59, microsecond=999999)) - minEndtime = datetime.time(hour=23, minute=59, second=59, microsecond=999999) - if str(params['endtime']) == str(today_Endtime): + tomorrow_Endtime = today_Endtime + (60*60*24) + minEndtime = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)#(hour=23, minute=59, second=59, microsecond=999999) + if str(params['endtime']) == str(today_Endtime) or str(params['endtime'])==tomorrow_Endtime: for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, - second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond) + currTime = datetime.datetime(year=tr.stats.endtime.year, month=tr.stats.endtime.month, day=tr.stats.endtime.day, + hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, + second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond, tzinfo=datetime.timezone.utc) if currTime < minEndtime: minEndtime = currTime - newEndtime = obspy.UTCDateTime(datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, - day = params['acq_date'].day, + newEndtime = obspy.UTCDateTime(datetime.datetime(year=minEndtime.year, month=minEndtime.month, + day = minEndtime.day, hour=minEndtime.hour, minute=minEndtime.minute, - second=minEndtime.second, microsecond=minEndtime.microsecond)) + second=minEndtime.second, microsecond=minEndtime.microsecond, tzinfo=datetime.timezone.utc)) params['endtime'] = newEndtime params['params']['endtime'] = newEndtime @@ -1225,10 +1291,12 @@

Parameters

#Remerge data dataIN = dataIN.merge(method=1) + #Plot the input stream? if plot_input_stream: try: params['InputPlot'] = _plot_specgram_stream(stream=dataIN, params=params, component='Z', stack_type='linear', detrend='mean', dbscale=True, fill_gaps=None, ylimstd=3, return_fig=True, fig=None, ax=None, show_plot=False) - _get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + #_get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + plt.show() except: print('Error with default plotting method, falling back to internal obspy plotting method') dataIN.plot(method='full', linewidth=0.25) @@ -1239,6 +1307,7 @@

Parameters

else: dataIN = _sort_channels(input=dataIN, source=source, verbose=verbose) + #Clean up the ends of the data unless explicitly specified to do otherwise (this is a kwarg, not a parameter) if 'clean_ends' not in kwargs.keys(): clean_ends=True else: @@ -1546,6 +1615,8 @@

Returns

ppsd_kwargs_sprit_defaults['skip_on_gaps'] = True if 'period_step_octaves' not in ppsd_kwargs: ppsd_kwargs_sprit_defaults['period_step_octaves'] = 0.03125 + if 'period_limits' not in ppsd_kwargs: + ppsd_kwargs_sprit_defaults['period_limits'] = [1/hvsr_data['hvsr_band'][1], 1/hvsr_data['hvsr_band'][0]] #Get Probablistic power spectral densities (PPSDs) #Get default args for function @@ -1559,7 +1630,6 @@

Returns

ppsd_kwargs = get_default_args(PPSD) ppsd_kwargs.update(ppsd_kwargs_sprit_defaults)#Update with sprit defaults, or user input - orig_args['ppsd_kwargs'] = [ppsd_kwargs] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): @@ -1594,7 +1664,7 @@

Returns

hvsr_data[site_name]['ProcessingStatus']['OverallStatus'] = False return hvsr_data else: - paz=hvsr_data['paz'] + paz = hvsr_data['paz'] stream = hvsr_data['stream'] #Get ppsds of e component @@ -1729,10 +1799,18 @@

Returns

hvsrDF['TimesProcessed_MPLEnd'] = hvsrDF['TimesProcessed_MPL'] + (ppsd_kwargs['ppsd_length']/86400) hvsrDF['Use'] = True + hvsrDF['Use']=hvsrDF['Use'].astype(bool) for gap in hvsr_data['ppsds']['Z']['times_gaps']: - hvsrDF['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ - (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ - + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'].gt(gap[1].matplotlib_date))| \ + (hvsrDF['TimesProcessed_MPLEnd'].lt(gap[0].matplotlib_date))# | \ + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + if 'xwindows_out' in hvsr_data.keys(): + for window in hvsr_data['xwindows_out']: + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].lt(window[0]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].lt(window[0]) )| \ + (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].gt(window[1]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].gt(window[1])) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + hvsrDF.set_index('TimesProcessed', inplace=True) hvsr_data['hvsr_df'] = hvsrDF #Create dict entry to keep track of how many outlier hvsr curves are removed (2-item list with [0]=current number, [1]=original number of curves) @@ -1747,6 +1825,11 @@

Returns

hvsr_data = sprit_utils.make_it_classy(hvsr_data) + hvsr_data['processing_parameters'] = {} + hvsr_data['processing_parameters']['generate_ppsds'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['generate_ppsds'][key] = value + hvsr_data['ProcessingStatus']['PPSDStatus'] = True hvsr_data = _check_processing_status(hvsr_data) return hvsr_data
@@ -1818,14 +1901,57 @@

Returns

params : dict Modified input dictionary with additional key:value pair containing paz dictionary (key = "paz") """ - invPath = params['metapath'] raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'trm', 't'] if params['instrument'].lower() in raspShakeInstNameList: if update_metadata: params = _update_shake_metadata(filepath=invPath, params=params, write_path=write_path) params = _read_RS_Metadata(params, source=source) + elif params['instrument'].lower() in trominoNameList: + params['paz'] = {'Z':{}, 'E':{}, 'N':{}} + #ALL THESE VALUES ARE PLACEHOLDERS, taken from RASPBERRY SHAKE! (Needed for PPSDs) + params['paz']['Z'] = {'sensitivity': 360000000.0, + 'gain': 360000000.0, + 'poles': [(-1+0j), (-3.03+0j), (-3.03+0j), (-666.67+0j)], + 'zeros': [0j, 0j, 0j]} + params['paz']['E'] = params['paz']['Z'] + params['paz']['N'] = params['paz']['Z'] + + channelObj_Z = obspy.core.inventory.channel.Channel(code='BHZ', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=90, types=None, external_references=None, + sample_rate=None, sample_rate_ratio_number_samples=None, sample_rate_ratio_number_seconds=None, + storage_format=None, clock_drift_in_seconds_per_sample=None, calibration_units=None, + calibration_units_description=None, sensor=None, pre_amplifier=None, data_logger=None, + equipments=None, response=None, description=None, comments=None, start_date=None, end_date=None, + restricted_status=None, alternate_code=None, historical_code=None, data_availability=None, + identifiers=None, water_level=None, source_id=None) + channelObj_E = obspy.core.inventory.channel.Channel(code='BHE', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=90, dip=0) + + channelObj_N = obspy.core.inventory.channel.Channel(code='BHN', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=0) + + siteObj = obspy.core.inventory.util.Site(name=params['params']['site'], description=None, town=None, county=None, region=None, country=None) + stationObj = obspy.core.inventory.station.Station(code='TZ', latitude=params['params']['latitude'], longitude=params['params']['longitude'], + elevation=params['params']['elevation'], channels=[channelObj_Z, channelObj_E, channelObj_N], site=siteObj, + vault=None, geology=None, equipments=None, operators=None, creation_date=datetime.datetime.today(), + termination_date=None, total_number_of_channels=None, + selected_number_of_channels=None, description='Estimated data for Tromino, this is NOT from the manufacturer', + comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, water_level=None, source_id=None) + + network = [obspy.core.inventory.network.Network(code='TROM', stations=[stationObj], total_number_of_stations=None, + selected_number_of_stations=None, description=None, comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, operators=None, source_id=None)] + + params['inv'] = obspy.Inventory(networks=network) else: if not invPath: pass #if invPath is None @@ -1931,6 +2057,10 @@

Returns

#Curve pass? orig_args = locals().copy() #Get the initial arguments + hvsr_results['processing_parameters']['get_report'] = {} + for key, value in orig_args.items(): + hvsr_results['processing_parameters']['get_report'][key] = value + if (verbose and isinstance(hvsr_results, HVSRBatch)) or (verbose and not hvsr_results['batch']): if isinstance(hvsr_results, HVSRData) and hvsr_results['batch']: pass @@ -2000,8 +2130,8 @@

Returns

curvePass = curvTestsPassed > 2 #Peak Pass? - peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ @@ -2136,24 +2266,25 @@

Returns

report_string_list.append(internalSeparator) report_string_list.append('') + justSize=34 #Print individual results report_string_list.append('\tCurve Tests: {}/3 passed (3/3 needed)'.format(curvTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]} Length of processing windows: {hvsr_results['BestPeak']['Report']['Lw']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]} Number of significant cycles: {hvsr_results['BestPeak']['Report']['Nc']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]} Low StDev. of H/V Curve over time: {hvsr_results['BestPeak']['Report']['σ_A(f)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]}"+" Length of processing windows".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Lw']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]}"+" Number of significant cycles".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Nc']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]}"+" Small H/V StDev over time".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['σ_A(f)']}") report_string_list.append('') report_string_list.append("\tPeak Tests: {}/6 passed (5/6 needed)".format(peakTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]} Clarity Below Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f-)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]} Clarity Above Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f+)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]} Clarity of Peak Amplitude: {hvsr_results['BestPeak']['Report']['A0']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]}"+" Peak is prominent below".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f-)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]}"+" Peak is prominent above".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f+)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]}"+" Peak is large".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A0']}") if hvsr_results['BestPeak']['PassList']['FreqStability']: res = sprit_utils.check_mark() else: res = sprit_utils.x_mark() - report_string_list.append(f"\t\t {res} Stability of Peak Freq. Over time: {hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]} Stability of Peak (Freq. StDev): {hvsr_results['BestPeak']['Report']['Sf']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]} Stability of Peak (Amp. StDev): {hvsr_results['BestPeak']['Report']['Sa']}") + report_string_list.append(f"\t\t {res}"+ " Peak freq. is stable over time".ljust(justSize)+ f"{hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]}"+" Stability of peak (Freq. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sf']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]}"+" Stability of peak (Amp. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sa']}") report_string_list.append('') report_string_list.append(f"Calculated using {hvsr_results['hvsr_df']['Use'].sum()}/{hvsr_results['hvsr_df']['Use'].count()} time windows".rjust(sepLen-1)) report_string_list.append(extSiteSeparator) @@ -2177,7 +2308,7 @@

Returns

import pandas as pd pdCols = ['Site Name', 'Acq_Date', 'Longitude', 'Latitide', 'Elevation', 'PeakFrequency', 'WindowLengthFreq.','SignificantCycles','LowCurveStDevOverTime', - 'PeakFreqClarityBelow','PeakFreqClarityAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] + 'PeakProminenceBelow','PeakProminenceAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] d = hvsr_results criteriaList = [] for p in hvsr_results['BestPeak']["PassList"]: @@ -2365,7 +2496,7 @@

Returns

-def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-19', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath='', hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], verbose=False) +def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-28', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath='', hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], instrument_settings=None, verbose=False)

Function for designating input parameters for reading in and processing data

@@ -2415,6 +2546,10 @@

Parameters

Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later.
peak_freq_range : list or tuple, default=[0.4, 40]
Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range.
+
instrument_settings : None, str, default=None
+
The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. +If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). +The default settings can be reset using the save_settings() function with the parameter settings_path='default'.
verbose : bool, default=False
Whether to print output and results to terminal
@@ -2448,6 +2583,7 @@

Returns

metapath = '', hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], + instrument_settings=None, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -2498,6 +2634,10 @@

Returns

Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. + instrument_settings : None, str, default=None + The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. + If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). + The default settings can be reset using the save_settings() function with the parameter settings_path='default'. verbose : bool, default=False Whether to print output and results to terminal @@ -2641,6 +2781,24 @@

Returns

'ProcessingStatus':{'InputStatus':True, 'OverallStatus':True} } + #Replace any default parameter settings with those from json file of interest, potentially + if instrument_settings is None: + instrument_settings_dict = {} + elif instrument_settings == "default" or instrument_settings is True: + # Update inputParamDict with default file + default_settings_json = settings_dir.joinpath('instrument_settings.json') + with open(default_settings_json.as_posix(), 'r') as f: + instrument_settings_dict = json.load(f) + else: + # Update inputParamDict with file + with open(instrument_settings, 'r') as f: + instrument_settings_dict = json.load(f) + + for settingName in instrument_settings_dict.keys(): + if settingName in inputParamDict.keys(): + inputParamDict[settingName] = instrument_settings_dict[settingName] + + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True params = _check_processing_status(params) @@ -2658,6 +2816,16 @@

Returns

def make_it_classy(input_data, verbose=False):
     if isinstance(input_data, (sprit_hvsr.HVSRData, sprit_hvsr.HVSRBatch)):
+        for k, v in input_data.items():
+            if k=='input_params':
+                for kin in input_data['input_params'].keys():
+                    if kin not in input_data.keys():
+                        input_data[kin] = input_data['input_params'][kin]
+            if k=='params':
+                for kin in input_data['params'].keys():
+                    print(kin)
+                    if kin not in input_data.keys():
+                        input_data[kin] = input_data['params'][kin]                
         output_class = input_data
     else:
         output_class = sprit_hvsr.HVSRData(input_data)
@@ -2667,7 +2835,7 @@ 

Returns

-def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, xtype='freq', fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True, **kwargs) +def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True, **kwargs)

Function to plot HVSR data

@@ -2676,29 +2844,25 @@

Parameters

hvsr_data : dict
Dictionary containing output from process_hvsr function
-
plot_type : str='HVSR' or list -
+
plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC'
The plot_type of plot(s) to plot. If list, will plot all plots listed -'HVSR' : Standard HVSR plot, including standard deviation -- '[HVSR] p' : HVSR plot with BestPeaks shown -- '[HVSR] p' : HVSR plot with best picked peak shown -
-- '[HVSR] p all' : HVSR plot with all picked peaks shown -
-- '[HVSR] p
t' : HVSR plot with peaks from all time steps in background -
-- '[HVSR p* ann] : Annotates plot with peaks -- '[HVSR] -s' : HVSR plots don't show standard deviation -- '[HVSR] t' : HVSR plot with individual hv curves for each time step shown -- '[HVSR] c' : HVSR plot with each components' spectra. Recommended to do this last (or just before 'specgram'), since doing c+ can make the component chart its own chart -'Specgram' : Combined spectrogram of all components -- '[spec]' : basic spectrogram plot of H/V curve
+- 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below: +- 'p' shows a vertical dotted line at frequency of the "best" peak +- 'ann' annotates the frequency value of of the "best" peak +- 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) +- 't' shows the H/V curve for all time windows +-'tp' shows all the peaks from the H/V curves of all the time windows +- 'COMP' - plot of the PPSD curves for each individual component ("C" also works) +- '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR) +- 'p' shows a vertical dotted line at frequency of the "best" peak +- 'ann' annotates the frequency value of of the "best" peak +- 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) +- 't' shows the H/V curve for all time windows +- 'SPEC' - spectrogram style plot of the H/V curve over time +- 'p' shows a horizontal dotted line at the frequency of the "best" peak +- 'ann' annotates the frequency value of the "best" peak
use_subplots : bool, default = True
Whether to output the plots as subplots (True) or as separate plots (False)
-
xtype : str, default = 'freq'
-
String for what to use, between frequency or period -For frequency, the following are accepted (case does not matter): 'f', 'Hz', 'freq', 'frequency' -For period, the following are accepted (case does not matter): 'p', 'T', 's', 'sec', 'second', 'per', 'period'
fig : matplotlib.Figure, default = None
If not None, matplotlib figure on which plot is plotted
ax : matplotlib.Axis, default = None
@@ -2729,32 +2893,32 @@

Returns

Expand source code -
def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, xtype='freq', fig=None, ax=None, return_fig=False,  save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs):
+
def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False,  save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs):
     """Function to plot HVSR data
 
     Parameters
     ----------
     hvsr_data : dict                  
         Dictionary containing output from process_hvsr function
-    plot_type : str='HVSR' or list    
+    plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC'
         The plot_type of plot(s) to plot. If list, will plot all plots listed
-        'HVSR' : Standard HVSR plot, including standard deviation
-        - '[HVSR] p' : HVSR plot with BestPeaks shown
-        - '[HVSR] p' : HVSR plot with best picked peak shown                
-        - '[HVSR] p* all' : HVSR plot with all picked peaks shown                
-        - '[HVSR] p* t' : HVSR plot with peaks from all time steps in background                
-        - '[HVSR p* ann] : Annotates plot with peaks
-        - '[HVSR] -s' : HVSR plots don't show standard deviation
-        - '[HVSR] t' : HVSR plot with individual hv curves for each time step shown
-        - '[HVSR] c' : HVSR plot with each components' spectra. Recommended to do this last (or just before 'specgram'), since doing c+ can make the component chart its own chart
-        'Specgram' : Combined spectrogram of all components
-        - '[spec]' : basic spectrogram plot of H/V curve
+        - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below:
+            - 'p' shows a vertical dotted line at frequency of the "best" peak
+            - 'ann' annotates the frequency value of of the "best" peak
+            - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified)
+            - 't' shows the H/V curve for all time windows
+                -'tp' shows all the peaks from the H/V curves of all the time windows
+        - 'COMP' - plot of the PPSD curves for each individual component ("C" also works)
+            - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR)
+            - 'p' shows a vertical dotted line at frequency of the "best" peak
+            - 'ann' annotates the frequency value of of the "best" peak
+            - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified)
+            - 't' shows the H/V curve for all time windows
+        - 'SPEC' - spectrogram style plot of the H/V curve over time
+            - 'p' shows a horizontal dotted line at the frequency of the "best" peak
+            - 'ann' annotates the frequency value of the "best" peak
     use_subplots : bool, default = True
         Whether to output the plots as subplots (True) or as separate plots (False)
-    xtype : str, default = 'freq'    
-        String for what to use, between frequency or period
-            For frequency, the following are accepted (case does not matter): 'f', 'Hz', 'freq', 'frequency'
-            For period, the following are accepted (case does not matter): 'p', 'T', 's', 'sec', 'second', 'per', 'period'
     fig : matplotlib.Figure, default = None
         If not None, matplotlib figure on which plot is plotted
     ax : matplotlib.Axis, default = None
@@ -2873,12 +3037,17 @@ 

Returns

fig, axis = plt.subplots() if p == 'hvsr': - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax) + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) elif p=='comp': plotComponents[0] = plotComponents[0][:-1] - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax) + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) elif p=='spec': - _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False) + plottypeKwargs = {} + for c in plotComponents: + print(c) + plottypeKwargs[c] = True + kwargs.update(plottypeKwargs) + _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) else: warnings.warn('Plot type {p} not recognized', UserWarning) @@ -3062,10 +3231,8 @@

Returns

xValMin = min(ppsds[k]['period_bin_centers']) xValMax = max(ppsds[k]['period_bin_centers']) - #Resample period bin values x_periods[k] = np.logspace(np.log10(xValMin), np.log10(xValMax), num=resample) - if smooth or type(smooth) is int: if smooth: smooth = 51 #Default smoothing window @@ -3093,7 +3260,6 @@

Returns

#Get average psd value across time for each channel (used to calc main H/V curve) psdValsTAvg[k] = np.nanmean(np.array(psdRaw[k]), axis=0) x_freqs[k] = np.divide(np.ones_like(x_periods[k]), x_periods[k]) - stDev[k] = np.std(psdRaw[k], axis=0) stDevValsM[k] = np.array(psdValsTAvg[k] - stDev[k]) stDevValsP[k] = np.array(psdValsTAvg[k] + stDev[k]) @@ -3111,9 +3277,8 @@

Returns

anyK = list(x_freqs.keys())[0] hvsr_curve, _ = __get_hvsr_curve(x=x_freqs[anyK], psd=psdValsTAvg, method=methodInt, hvsr_data=hvsr_data, verbose=verbose) origPPSD = hvsr_data['ppsds_obspy'].copy() - #Add some other variables to our output dictionary - hvsr_data = {'input_params':hvsr_data, + hvsr_dataUpdate = {'input_params':hvsr_data, 'x_freqs':x_freqs, 'hvsr_curve':hvsr_curve, 'x_period':x_periods, @@ -3130,7 +3295,7 @@

Returns

'hvsr_df':hvsr_data['hvsr_df'] } - hvsr_out = HVSRData(hvsr_data) + hvsr_out = HVSRData(hvsr_dataUpdate) #This is if manual editing was used (should probably be updated at some point to just use masks) if 'xwindows_out' in hvsr_data.keys(): @@ -3204,7 +3369,6 @@

Returns

bool_col='Use' eval_col='HV_Curves' - testCol = hvsr_out['hvsr_df'].loc[hvsr_out['hvsr_df'][bool_col], eval_col].apply(np.nanstd).gt((avg_stdT + (std_stdT * outlier_curve_std))) low_std_val = avg_stdT - (std_stdT * outlier_curve_std) hi_std_val = avg_stdT + (std_stdT * outlier_curve_std) @@ -3258,16 +3422,15 @@

Returns

hvsr_out = __gethvsrparams(hvsr_out) #Include the original obspy stream in the output - #print(hvsr_data.keys()) - #print(type(hvsr_data)) - #print(hvsr_data['input_params'].keys()) - hvsr_out['input_stream'] = hvsr_data['input_params']['input_stream'] #input_stream - + hvsr_out['input_stream'] = hvsr_dataUpdate['input_params']['input_stream'] #input_stream hvsr_out = sprit_utils.make_it_classy(hvsr_out) - hvsr_out['ProcessingStatus']['HVStatus'] = True hvsr_out = _check_processing_status(hvsr_out) + hvsr_data['processing_parameters']['process_hvsr'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['process_hvsr'][key] = value + return hvsr_out
@@ -3566,12 +3729,15 @@

Returns

#Add output if isinstance(output, (HVSRData, dict)): - output['stream'] = outStream + if isinstance(outStream, (obspy.Stream, obspy.Trace)): + output['stream'] = outStream + else: + output['stream'] = outStream['stream'] output['input_stream'] = hvsr_data['input_stream'] output['ProcessingStatus']['RemoveNoiseStatus'] = True output = _check_processing_status(output) - if 'hvsr_df' in output.keys(): + if 'hvsr_df' in output.keys() or ('params' in output.keys() and 'hvsr_df' in output['params'].keys())or ('input_params' in output.keys() and 'hvsr_df' in output['input_params'].keys()): hvsrDF = output['hvsr_df'] outStream = output['stream'].split() @@ -3585,8 +3751,10 @@

Returns

if trEndTime < trStartTime and comp_end==comp_start: gap = [trEndTime,trStartTime] + output['hvsr_df']['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ + output['hvsr_df']['Use'] = output['hvsr_df']['Use'].astype(bool) trEndTime = trace.stats.endtime @@ -3597,8 +3765,10 @@

Returns

else: warnings.warn(f"Output of type {type(output)} for this function will likely result in errors in other processing steps. Returning hvsr_data data.") return hvsr_data - - + + output = sprit_utils.make_it_classy(output) + if 'xwindows_out' not in output.keys(): + output['xwindows_out'] = [] return output
@@ -3697,8 +3867,14 @@

Raises

RuntimeError If the data being processed is a single file, an error will be raised if generate_ppsds() does not work correctly. No errors are raised for remove_noise() errors (since that is an optional step) and the process_hvsr() step (since that is the last processing step) . """ + if 'hvsr_band' not in kwargs.keys(): + kwargs['hvsr_band'] = inspect.signature(input_params).parameters['hvsr_band'].default + if 'peak_freq_range' not in kwargs.keys(): + kwargs['peak_freq_range'] = inspect.signature(input_params).parameters['peak_freq_range'].default + #Get the input parameters - input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in input_params.__code__.co_varnames} + input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(input_params).parameters.keys())} + try: params = input_params(datapath=datapath, verbose=verbose, **input_params_kwargs) except: @@ -3711,15 +3887,14 @@

Raises

#Fetch Data try: - fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in fetch_data.__code__.co_varnames} + fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(fetch_data).parameters.keys())} dataIN = fetch_data(params=params, source=source, verbose=verbose, **fetch_data_kwargs) except: #Even if batch, this is reading in data for all sites so we want to raise error, not just warn raise RuntimeError('Data not read correctly, see sprit.fetch_data() function and parameters for more details.') - #Remove Noise try: - remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in remove_noise.__code__.co_varnames} + remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(remove_noise).parameters.keys())} data_noiseRemoved = remove_noise(hvsr_data=dataIN, verbose=verbose,**remove_noise_kwargs) except: data_noiseRemoved = dataIN @@ -3743,8 +3918,8 @@

Raises

#Generate PPSDs try: - generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in generate_ppsds.__code__.co_varnames} - PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in PPSD.__init__.__code__.co_varnames} + generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(generate_ppsds).parameters.keys())} + PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(PPSD).parameters.keys())} generate_ppsds_kwargs.update(PPSDkwargs) ppsd_data = generate_ppsds(hvsr_data=data_noiseRemoved, verbose=verbose,**generate_ppsds_kwargs) except Exception as e: @@ -3770,7 +3945,7 @@

Raises

#Process HVSR Curves try: - process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in process_hvsr.__code__.co_varnames} + process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(process_hvsr).parameters.keys())} hvsr_results = process_hvsr(hvsr_data=ppsd_data, verbose=verbose,**process_hvsr_kwargs) except Exception as e: traceback.print_exception(sys.exc_info()[1]) @@ -3800,10 +3975,10 @@

Raises

#Final post-processing/reporting #Check peaks - check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in check_peaks.__code__.co_varnames} + check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(check_peaks).parameters.keys())} hvsr_results = check_peaks(hvsr_data=hvsr_results, verbose=verbose, **check_peaks_kwargs) - get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in get_report.__code__.co_varnames} + get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(get_report).parameters.keys())} get_report(hvsr_results=hvsr_results, verbose=verbose, **get_report_kwargs) if verbose: @@ -3822,8 +3997,9 @@

Raises

#We do not need to plot another report if already plotted pass else: - hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} - hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, **hvplot_kwargs) + #hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} + #hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, return_fig=True, show=False, close_figs=True) + pass else: pass @@ -3841,6 +4017,106 @@

Raises

return hvsr_results
+
+def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False) +
+
+

Save settings to json file

+

Parameters

+
+
settings_path : str, default="default"
+
Where to save the json file(s) containing the settings, by default 'default'. +If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. +If 'all' is selected, a directory should be supplied. +Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
+
settings_type : str, {'all', 'instrument', 'processing'}
+
What kind of settings to save. +If 'all', saves all possible types in their respective json files. +If 'instrument', save the instrument settings to their respective file. +If 'processing', saves the processing settings to their respective file. By default 'all'
+
verbose : bool, default=True
+
Whether to print outputs and information to the terminal
+
+
+ +Expand source code + +
def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False):
+    """Save settings to json file
+
+    Parameters
+    ----------
+    settings_path : str, default="default"
+        Where to save the json file(s) containing the settings, by default 'default'. 
+        If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to.
+        If 'all' is selected, a directory should be supplied. 
+        Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
+    settings_type : str, {'all', 'instrument', 'processing'}
+        What kind of settings to save. 
+        If 'all', saves all possible types in their respective json files.
+        If 'instrument', save the instrument settings to their respective file.
+        If 'processing', saves the processing settings to their respective file. By default 'all'
+    verbose : bool, default=True
+        Whether to print outputs and information to the terminal
+
+    """
+    fnameDict = {}
+    fnameDict['instrument'] = "instrument_settings.json"
+    fnameDict['processing'] = "processing_settings.json"
+
+    if settings_path == 'default' or settings_path is True:
+        settingsPath = resource_dir.joinpath('settings')
+    else:
+        settings_path = pathlib.Path(settings_path)
+        if not settings_path.exists():
+            if not settings_path.parent.exists():
+                print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}')
+                settingsPath = pathlib.Path.home()
+            else:
+                settingsPath = settings_path.parent
+        
+        if settings_path.is_dir():
+            settingsPath = settings_path
+        elif settings_path.is_file():
+            settingsPath = settings_path.parent
+            fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json"
+            fnameDict['processing'] = settings_path.name+"_processingSettings.json"
+
+    #Get final filepaths        
+    instSetFPath = settingsPath.joinpath(fnameDict['instrument'])
+    procSetFPath = settingsPath.joinpath(fnameDict['processing'])
+
+    #Get settings values
+    instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"]
+    procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report]
+
+    instrument_settings_dict = {}
+    processing_settings_dict = {}
+
+    for k in instKeys:
+        if isinstance(hvsr_data[k], pathlib.PurePath):
+            instrument_settings_dict[k] = hvsr_data[k].as_posix()
+        else:
+            instrument_settings_dict[k] = hvsr_data[k]
+    
+    for func in procFuncs:
+        funcName = func.__name__
+        processing_settings_dict[funcName] = {}
+        for arg in inspect.getfullargspec(func)[0]:
+            if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)):
+                pass
+            else:
+                processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg]
+    
+    #Save settings files
+    if settings_type.lower()=='instrument' or settings_type.lower()=='all':
+        with open(instSetFPath.as_posix(), 'w') as instSetF:
+            json.dump(instrument_settings_dict, instSetF)
+    if settings_type.lower()=='processing' or settings_type.lower()=='all':
+        with open(procSetFPath.as_posix(), 'w') as procSetF:
+            json.dump(processing_settings_dict, procSetF)        
+
+
def time_it(_t, proc_name='', verbose=True)
@@ -4285,7 +4561,9 @@

Returns

Parameters ---------- export_path : filepath, default=True - Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True + Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). + By default True. + If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True ext : str, optional The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension. """ @@ -4607,7 +4885,9 @@

Parameters

Parameters

export_path : filepath, default=True
-
Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True
+
Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). +By default True. +If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True
ext : str, optional
The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension.
@@ -4621,7 +4901,9 @@

Parameters

Parameters ---------- export_path : filepath, default=True - Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True + Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). + By default True. + If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True ext : str, optional The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension. """ @@ -4797,6 +5079,7 @@

Index

  • read_from_RS
  • remove_noise
  • run
  • +
  • save_settings
  • time_it
  • diff --git a/docs/sprit_gui.html b/docs/sprit_gui.html index 7c43f10..9635e6d 100644 --- a/docs/sprit_gui.html +++ b/docs/sprit_gui.html @@ -32,6 +32,7 @@

    Module sprit.sprit_gui

    import datetime import functools import linecache +import json import os import pathlib import pkg_resources @@ -63,6 +64,14 @@

    Module sprit.sprit_gui

    pass global spritApp +global current_theme_name + +resource_dir = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/')) +settings_dir = resource_dir.joinpath('settings') +gui_theme_file = settings_dir.joinpath('gui_theme.json') +with open(gui_theme_file, 'r') as f: + curr_gui_dict = json.load(f) +current_theme_name = curr_gui_dict['theme_name'] #Decorator that catches errors and warnings (to be modified later for gui) def catch_errors(func): @@ -180,10 +189,11 @@

    Module sprit.sprit_gui

    self.darkthemepath = pathlib.Path(pkg_resources.resource_filename(__name__, "resources/themes/forest-dark.tcl")) self.lightthemepath = pathlib.Path(pkg_resources.resource_filename(__name__, "resources/themes/forest-light.tcl")) + + # Create the style object self.style = ttk.Style(master) - self.master.tk.call('source', self.lightthemepath) - #self.style.theme_use('default') + # #self.style.theme_use('forest-light') self.create_menubar() @@ -193,6 +203,13 @@

    Module sprit.sprit_gui

    self.master.columnconfigure(0, weight=1) + if 'forest' in current_theme_name: + if 'light' in current_theme_name: + self.master.tk.call('source', self.lightthemepath) + else: + self.master.tk.call('source', self.darkthemepath) + else: + self.style.theme_use(current_theme_name) # Create the dark theme #self.style.theme_create("dark", parent="alt", settings={ # "TLabel": {"configure": {"background": "black", "foreground": "white"}}, @@ -240,7 +257,33 @@

    Module sprit.sprit_gui

    def on_theme_select(): # Set the theme based on the selected value self.style = ttk.Style() - + + #Update the theme file so the new theme opens on reboot + prev_theme = curr_gui_dict['theme_name'] + curr_gui_dict['theme_name'] = self.theme_var.get() + with open(gui_theme_file, 'w') as f: + json.dump(curr_gui_dict, f) + + def apply_theme(): + if 'forest' in self.theme_var.get(): + if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): + self.master.tk.call('source', self.darkthemepath) + elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): + self.master.tk.call('source', self.lightthemepath) + self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + + if curr_gui_dict['theme_name']=='forest-light' or curr_gui_dict['theme_name'] == 'forest-dark': + do_reboot = messagebox.askyesno('App Restart Required', + f"It is recommended to restart the SpRIT GUI at this time to apply this theme. If not, you may continue but theme errors may occur. Click No to retain current theme ({prev_theme}) \nReboot now?", + ) + print(do_reboot) + if do_reboot: + reboot_app() + else: + self.theme_var.set(prev_theme) + else: + apply_theme() + """An attempt to get the backgrounds right def apply_to_all_children(widget, func): Recursively apply a function to all child widgets of a given widget @@ -258,12 +301,7 @@

    Module sprit.sprit_gui

    apply_to_all_children(self.master, change_background_color) """ - if 'forest' in self.theme_var.get(): - if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): - self.master.tk.call('source', self.darkthemepath) - elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): - self.master.tk.call('source', self.lightthemepath) - self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + #self.master.tk.call("ttk::setTheme", self.theme_var.get()) #self.style.theme_use(self.theme_var.get()) @@ -279,7 +317,7 @@

    Module sprit.sprit_gui

    filepath = filedialog.asksaveasfilename() self.theme_menu = tk.Menu(self.menubar, tearoff=0) - self.theme_var = tk.StringVar(value="Default") + self.theme_var = tk.StringVar(value=current_theme_name) self.theme_menu.add_radiobutton(label="Default", variable=self.theme_var, value="default", command=on_theme_select) self.theme_menu.add_radiobutton(label="Clam", variable=self.theme_var, value="clam", command=on_theme_select) self.theme_menu.add_radiobutton(label="Alt", variable=self.theme_var, value="alt", command=on_theme_select) @@ -524,8 +562,8 @@

    Module sprit.sprit_gui

    self.peakTest6ResultText.configure(text=hvsr_results['BestPeak']['Report']['Sa'][:-1]) self.peakTest6Result.configure(text=hvsr_results['BestPeak']['Report']['Sa'][-1]) - peakPass = (hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakPass = (hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ @@ -3065,6 +3103,13 @@

    Module sprit.sprit_gui

    root.destroy() exit() +def reboot_app(): + """Restarts the current program. + Note: this function does not return. Any cleanup action (like + saving data) must be done before calling this function.""" + python = sys.executable + os.execl(python, python, * sys.argv) + if __name__ == "__main__": can_gui = sprit_utils.check_gui_requirements() @@ -3226,6 +3271,25 @@

    Functions

    exit()
    +
    +def reboot_app() +
    +
    +

    Restarts the current program. +Note: this function does not return. Any cleanup action (like +saving data) must be done before calling this function.

    +
    + +Expand source code + +
    def reboot_app():
    +    """Restarts the current program.
    +    Note: this function does not return. Any cleanup action (like
    +    saving data) must be done before calling this function."""
    +    python = sys.executable
    +    os.execl(python, python, * sys.argv)
    +
    +
    @@ -3251,10 +3315,11 @@

    Classes

    self.darkthemepath = pathlib.Path(pkg_resources.resource_filename(__name__, "resources/themes/forest-dark.tcl")) self.lightthemepath = pathlib.Path(pkg_resources.resource_filename(__name__, "resources/themes/forest-light.tcl")) + + # Create the style object self.style = ttk.Style(master) - self.master.tk.call('source', self.lightthemepath) - #self.style.theme_use('default') + # #self.style.theme_use('forest-light') self.create_menubar() @@ -3264,6 +3329,13 @@

    Classes

    self.master.columnconfigure(0, weight=1) + if 'forest' in current_theme_name: + if 'light' in current_theme_name: + self.master.tk.call('source', self.lightthemepath) + else: + self.master.tk.call('source', self.darkthemepath) + else: + self.style.theme_use(current_theme_name) # Create the dark theme #self.style.theme_create("dark", parent="alt", settings={ # "TLabel": {"configure": {"background": "black", "foreground": "white"}}, @@ -3311,7 +3383,33 @@

    Classes

    def on_theme_select(): # Set the theme based on the selected value self.style = ttk.Style() - + + #Update the theme file so the new theme opens on reboot + prev_theme = curr_gui_dict['theme_name'] + curr_gui_dict['theme_name'] = self.theme_var.get() + with open(gui_theme_file, 'w') as f: + json.dump(curr_gui_dict, f) + + def apply_theme(): + if 'forest' in self.theme_var.get(): + if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): + self.master.tk.call('source', self.darkthemepath) + elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): + self.master.tk.call('source', self.lightthemepath) + self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + + if curr_gui_dict['theme_name']=='forest-light' or curr_gui_dict['theme_name'] == 'forest-dark': + do_reboot = messagebox.askyesno('App Restart Required', + f"It is recommended to restart the SpRIT GUI at this time to apply this theme. If not, you may continue but theme errors may occur. Click No to retain current theme ({prev_theme}) \nReboot now?", + ) + print(do_reboot) + if do_reboot: + reboot_app() + else: + self.theme_var.set(prev_theme) + else: + apply_theme() + """An attempt to get the backgrounds right def apply_to_all_children(widget, func): Recursively apply a function to all child widgets of a given widget @@ -3329,12 +3427,7 @@

    Classes

    apply_to_all_children(self.master, change_background_color) """ - if 'forest' in self.theme_var.get(): - if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): - self.master.tk.call('source', self.darkthemepath) - elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): - self.master.tk.call('source', self.lightthemepath) - self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + #self.master.tk.call("ttk::setTheme", self.theme_var.get()) #self.style.theme_use(self.theme_var.get()) @@ -3350,7 +3443,7 @@

    Classes

    filepath = filedialog.asksaveasfilename() self.theme_menu = tk.Menu(self.menubar, tearoff=0) - self.theme_var = tk.StringVar(value="Default") + self.theme_var = tk.StringVar(value=current_theme_name) self.theme_menu.add_radiobutton(label="Default", variable=self.theme_var, value="default", command=on_theme_select) self.theme_menu.add_radiobutton(label="Clam", variable=self.theme_var, value="clam", command=on_theme_select) self.theme_menu.add_radiobutton(label="Alt", variable=self.theme_var, value="alt", command=on_theme_select) @@ -3595,8 +3688,8 @@

    Classes

    self.peakTest6ResultText.configure(text=hvsr_results['BestPeak']['Report']['Sa'][:-1]) self.peakTest6Result.configure(text=hvsr_results['BestPeak']['Report']['Sa'][-1]) - peakPass = (hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakPass = (hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ @@ -6151,7 +6244,33 @@

    Methods

    def on_theme_select(): # Set the theme based on the selected value self.style = ttk.Style() - + + #Update the theme file so the new theme opens on reboot + prev_theme = curr_gui_dict['theme_name'] + curr_gui_dict['theme_name'] = self.theme_var.get() + with open(gui_theme_file, 'w') as f: + json.dump(curr_gui_dict, f) + + def apply_theme(): + if 'forest' in self.theme_var.get(): + if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): + self.master.tk.call('source', self.darkthemepath) + elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): + self.master.tk.call('source', self.lightthemepath) + self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + + if curr_gui_dict['theme_name']=='forest-light' or curr_gui_dict['theme_name'] == 'forest-dark': + do_reboot = messagebox.askyesno('App Restart Required', + f"It is recommended to restart the SpRIT GUI at this time to apply this theme. If not, you may continue but theme errors may occur. Click No to retain current theme ({prev_theme}) \nReboot now?", + ) + print(do_reboot) + if do_reboot: + reboot_app() + else: + self.theme_var.set(prev_theme) + else: + apply_theme() + """An attempt to get the backgrounds right def apply_to_all_children(widget, func): Recursively apply a function to all child widgets of a given widget @@ -6169,12 +6288,7 @@

    Methods

    apply_to_all_children(self.master, change_background_color) """ - if 'forest' in self.theme_var.get(): - if self.theme_var.get()=='forest-dark' and 'forest-dark' not in self.style.theme_names(): - self.master.tk.call('source', self.darkthemepath) - elif self.theme_var.get()=='forest-light' and 'forest-light' not in self.style.theme_names(): - self.master.tk.call('source', self.lightthemepath) - self.master.tk.call("ttk::style", "theme", "use", self.theme_var.get()) + #self.master.tk.call("ttk::setTheme", self.theme_var.get()) #self.style.theme_use(self.theme_var.get()) @@ -6190,7 +6304,7 @@

    Methods

    filepath = filedialog.asksaveasfilename() self.theme_menu = tk.Menu(self.menubar, tearoff=0) - self.theme_var = tk.StringVar(value="Default") + self.theme_var = tk.StringVar(value=current_theme_name) self.theme_menu.add_radiobutton(label="Default", variable=self.theme_var, value="default", command=on_theme_select) self.theme_menu.add_radiobutton(label="Clam", variable=self.theme_var, value="clam", command=on_theme_select) self.theme_menu.add_radiobutton(label="Alt", variable=self.theme_var, value="alt", command=on_theme_select) @@ -6445,8 +6559,8 @@

    Methods

    self.peakTest6ResultText.configure(text=hvsr_results['BestPeak']['Report']['Sa'][:-1]) self.peakTest6Result.configure(text=hvsr_results['BestPeak']['Report']['Sa'][-1]) - peakPass = (hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakPass = (hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ @@ -9044,6 +9158,7 @@

    Index

  • Classes

    diff --git a/docs/sprit_hvsr.html b/docs/sprit_hvsr.html index 46374ff..c420748 100644 --- a/docs/sprit_hvsr.html +++ b/docs/sprit_hvsr.html @@ -45,6 +45,7 @@

    Module sprit.sprit_hvsr

    import pathlib import pickle import pkg_resources +import struct import tempfile import traceback import warnings @@ -79,7 +80,11 @@

    Module sprit.sprit_hvsr

    max_rank = 0 plotRows = 4 -sample_data_dir = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/sample_data/')) +#Get the main resources directory path, and the other paths as well +resource_dir = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/')) +sample_data_dir = resource_dir.joinpath('sample_data') +settings_dir = resource_dir.joinpath('settings') + sampleFileKeyMap = {'1':sample_data_dir.joinpath('SampleHVSRSite1_AM.RAC84.00.2023.046_2023-02-15_1704-1734.MSEED'), '2':sample_data_dir.joinpath('SampleHVSRSite2_AM.RAC84.00.2023-02-15_2132-2200.MSEED'), '3':sample_data_dir.joinpath('SampleHVSRSite3_AM.RAC84.00.2023.199_2023-07-18_1432-1455.MSEED'), @@ -313,7 +318,9 @@

    Module sprit.sprit_hvsr

    Parameters ---------- export_path : filepath, default=True - Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True + Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). + By default True. + If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True ext : str, optional The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension. """ @@ -576,8 +583,14 @@

    Module sprit.sprit_hvsr

    RuntimeError If the data being processed is a single file, an error will be raised if generate_ppsds() does not work correctly. No errors are raised for remove_noise() errors (since that is an optional step) and the process_hvsr() step (since that is the last processing step) . """ + if 'hvsr_band' not in kwargs.keys(): + kwargs['hvsr_band'] = inspect.signature(input_params).parameters['hvsr_band'].default + if 'peak_freq_range' not in kwargs.keys(): + kwargs['peak_freq_range'] = inspect.signature(input_params).parameters['peak_freq_range'].default + #Get the input parameters - input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in input_params.__code__.co_varnames} + input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(input_params).parameters.keys())} + try: params = input_params(datapath=datapath, verbose=verbose, **input_params_kwargs) except: @@ -590,15 +603,14 @@

    Module sprit.sprit_hvsr

    #Fetch Data try: - fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in fetch_data.__code__.co_varnames} + fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(fetch_data).parameters.keys())} dataIN = fetch_data(params=params, source=source, verbose=verbose, **fetch_data_kwargs) except: #Even if batch, this is reading in data for all sites so we want to raise error, not just warn raise RuntimeError('Data not read correctly, see sprit.fetch_data() function and parameters for more details.') - #Remove Noise try: - remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in remove_noise.__code__.co_varnames} + remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(remove_noise).parameters.keys())} data_noiseRemoved = remove_noise(hvsr_data=dataIN, verbose=verbose,**remove_noise_kwargs) except: data_noiseRemoved = dataIN @@ -622,8 +634,8 @@

    Module sprit.sprit_hvsr

    #Generate PPSDs try: - generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in generate_ppsds.__code__.co_varnames} - PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in PPSD.__init__.__code__.co_varnames} + generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(generate_ppsds).parameters.keys())} + PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(PPSD).parameters.keys())} generate_ppsds_kwargs.update(PPSDkwargs) ppsd_data = generate_ppsds(hvsr_data=data_noiseRemoved, verbose=verbose,**generate_ppsds_kwargs) except Exception as e: @@ -649,7 +661,7 @@

    Module sprit.sprit_hvsr

    #Process HVSR Curves try: - process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in process_hvsr.__code__.co_varnames} + process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(process_hvsr).parameters.keys())} hvsr_results = process_hvsr(hvsr_data=ppsd_data, verbose=verbose,**process_hvsr_kwargs) except Exception as e: traceback.print_exception(sys.exc_info()[1]) @@ -679,10 +691,10 @@

    Module sprit.sprit_hvsr

    #Final post-processing/reporting #Check peaks - check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in check_peaks.__code__.co_varnames} + check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(check_peaks).parameters.keys())} hvsr_results = check_peaks(hvsr_data=hvsr_results, verbose=verbose, **check_peaks_kwargs) - get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in get_report.__code__.co_varnames} + get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(get_report).parameters.keys())} get_report(hvsr_results=hvsr_results, verbose=verbose, **get_report_kwargs) if verbose: @@ -701,8 +713,9 @@

    Module sprit.sprit_hvsr

    #We do not need to plot another report if already plotted pass else: - hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} - hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, **hvplot_kwargs) + #hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} + #hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, return_fig=True, show=False, close_figs=True) + pass else: pass @@ -721,7 +734,7 @@

    Module sprit.sprit_hvsr

    #Quality checks, stability tests, clarity tests #def check_peaks(hvsr, x, y, index_list, peak, peakm, peakp, hvsr_peaks, stdf, hvsr_log_std, rank, hvsr_band=[0.4, 40], do_rank=False): -def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose=False): +def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_range=[1, 20], verbose=False): """Function to run tests on HVSR peaks to find best one and see if it passes quality checks Parameters @@ -730,6 +743,10 @@

    Module sprit.sprit_hvsr

    Dictionary containing all the calculated information about the HVSR data (i.e., hvsr_out returned from process_hvsr) hvsr_band : tuple or list, default=[0.4, 40] 2-item tuple or list with lower and upper limit of frequencies to analyze + peak_selection : str or numeric, default='max' + How to select the "best" peak used in the analysis. For peak_selection="max" (default value), the highest peak within peak_freq_range is used. + For peak_selection='scored', an algorithm is used to select the peak based in part on which peak passes the most SESAME criteria. + If a numeric value is used (e.g., int or float), this should be a frequency value to manually select as the peak of interest. peak_freq_range : tuple or list, default=[1, 20]; The frequency range within which to check for peaks. If there is an HVSR curve with multiple peaks, this allows the full range of data to be processed while limiting peak picks to likely range. verbose : bool, default=False @@ -742,6 +759,10 @@

    Module sprit.sprit_hvsr

    """ orig_args = locals().copy() #Get the initial arguments + hvsr_data['processing_parameters']['check_peaks'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['check_peaks'][key] = value + if (verbose and 'input_params' not in hvsr_data.keys()) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: pass @@ -777,13 +798,38 @@

    Module sprit.sprit_hvsr

    if hvsr_data['ProcessingStatus']['OverallStatus']: if not hvsr_band: hvsr_band = [0.4,40] + hvsr_data['hvsr_band'] = hvsr_band anyK = list(hvsr_data['x_freqs'].keys())[0] x = hvsr_data['x_freqs'][anyK] #Consistent for all curves y = hvsr_data['hvsr_curve'] #Calculated based on "Use" column - index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + + scorelist = ['score', 'scored', 'best', 's'] + maxlist = ['max', 'highest', 'm'] + # Convert peak_selection to numeric, get index of nearest value as list item for __init_peaks() + try: + peak_val = float(peak_selection) + index_list = [np.argmin(np.abs(x - peak_val))] + except: + # If score method is being used, get index list for __init_peaks() + if peak_selection in scorelist: + index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + elif peak_selection in maxlist: + #Get max index as item in list for __init_peaks() + startInd = np.argmin(np.abs(x - peak_freq_range[0])) + endInd = np.argmin(np.abs(x - peak_freq_range[1])) + if startInd > endInd: + holder = startInd + startInd = endInd + endInd = holder + subArrayMax = np.argmax(y[startInd:endInd]) + + # If max val is in subarray, this will be the same as the max of curve + # Otherwise, it will be the index of the value that is max within peak_freq_range + index_list = [subArrayMax+startInd] + hvsrp = hvsr_data['hvsrp'] #Calculated based on "Use" column hvsrm = hvsr_data['hvsrm'] #Calculated based on "Use" column @@ -809,7 +855,7 @@

    Module sprit.sprit_hvsr

    peakp = __init_peaks(x, hvsrp, index_p, hvsr_band, peak_freq_range) peakp = __check_clarity(x, hvsrp, peakp, do_rank=True) - #Do for hvsrm + # Do for hvsrm # Find the relative extrema of hvsrm (hvsr - 1 standard deviation) if not np.isnan(np.sum(hvsrm)): index_m = __find_peaks(hvsrm) @@ -819,6 +865,7 @@

    Module sprit.sprit_hvsr

    peakm = __init_peaks(x, hvsrm, index_m, hvsr_band, peak_freq_range) peakm = __check_clarity(x, hvsrm, peakm, do_rank=True) + # Get standard deviation of time peaks stdf = __get_stdf(x, index_list, hvsrPeaks) peak = __check_freq_stability(peak, peakm, peakp) @@ -830,7 +877,7 @@

    Module sprit.sprit_hvsr

    # Get the BestPeak based on the peak score # Calculate whether each peak passes enough tests curveTests = ['WindowLengthFreq.','SignificantCycles', 'LowCurveStDevOverTime'] - peakTests = ['PeakFreqClarityBelow', 'PeakFreqClarityAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] + peakTests = ['PeakProminenceBelow', 'PeakProminenceAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] bestPeakScore = 0 for p in hvsr_data['PeakReport']: @@ -864,6 +911,10 @@

    Module sprit.sprit_hvsr

    else: hvsr_data['BestPeak'] = {} print(f"Processing Errors: No Best Peak identified for {hvsr_data['site']}") + try: + hvsr_data.plot() + except: + pass return hvsr_data @@ -959,7 +1010,9 @@

    Module sprit.sprit_hvsr

    date=params['acq_date'] #Cleanup for gui input - if '}' in str(params['datapath']): + if isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass + elif '}' in str(params['datapath']): params['datapath'] = params['datapath'].as_posix().replace('{','') params['datapath'] = params['datapath'].split('}') @@ -972,7 +1025,10 @@

    Module sprit.sprit_hvsr

    #Make sure datapath is pointing to an actual file if isinstance(params['datapath'],list): for i, d in enumerate(params['datapath']): - params['datapath'][i] = sprit_utils.checkifpath(str(d).strip()) + params['datapath'][i] = sprit_utils.checkifpath(str(d).strip(), sample_list=sampleList) + dPath = params['datapath'] + elif isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass else: dPath = sprit_utils.checkifpath(params['datapath'], sample_list=sampleList) @@ -1024,15 +1080,26 @@

    Module sprit.sprit_hvsr

    #Select which instrument we are reading from (requires different processes for each instrument) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'tromino 3g', 'tromino 3g+', 'tr', 't'] + + #Get any kwargs that are included in obspy.read + obspyReadKwargs = {} + for argName in inspect.getfullargspec(obspy.read)[0]: + if argName in kwargs.keys(): + obspyReadKwargs[argName] = kwargs[argName] #Select how reading will be done if source=='raw': - if inst.lower() in raspShakeInstNameList: - try: + try: + if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) - except: - raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") - return params + + elif inst.lower() in trominoNameList: + rawDataIN = __read_tromino_files(dPath, params, verbose=verbose) + except: + raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") + elif source=='stream' or isinstance(params, (obspy.Stream, obspy.Trace)): + rawDataIN = params['datapath'].copy() elif source=='dir': if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) @@ -1043,28 +1110,27 @@

    Module sprit.sprit_hvsr

    for f in temp_file_glob: currParams = params currParams['datapath'] = f + curr_data = fetch_data(params, source='file', #all the same as input, except just reading the one file using the source='file' - trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs), + trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs) + curr_data.merge() obspyFiles[f.stem] = curr_data #Add path object to dict, with filepath's stem as the site name return HVSRBatch(obspyFiles) - elif source=='file' and str(params['datapath']).lower() not in sampleList: if isinstance(dPath, list) or isinstance(dPath, tuple): rawStreams = [] for datafile in dPath: - rawStream = obspy.read(datafile) + rawStream = obspy.read(datafile, **obspyReadKwargs) rawStreams.append(rawStream) #These are actually streams, not traces - for i, stream in enumerate(rawStreams): if i == 0: rawDataIN = obspy.Stream(stream) #Just in case else: rawDataIN = rawDataIN + stream #This adds a stream/trace to the current stream object - elif str(dPath)[:6].lower()=='sample': pass else: - rawDataIN = obspy.read(dPath)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) + rawDataIN = obspy.read(dPath, **obspyReadKwargs)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) import warnings with warnings.catch_warnings(): warnings.simplefilter(action='ignore', category=UserWarning) @@ -1111,12 +1177,15 @@

    Module sprit.sprit_hvsr

    except: RuntimeError(f'source={source} not recognized, and datapath cannot be read using obspy.read()') + #Get metadata from the data itself, if not reading raw data try: dataIN = rawDataIN.copy() if source!='raw': #Use metadata from file for; # site if params['site'] == "HVSR Site": + if isinstance(dPath, (list, tuple)): + dPath = dPath[0] params['site'] = dPath.stem params['params']['site'] = dPath.stem @@ -1153,11 +1222,13 @@

    Module sprit.sprit_hvsr

    today_Starttime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=0, minute=0, second=0, microsecond=0)) - maxStarttime = datetime.time(hour=0, minute=0, second=0, microsecond=0) + maxStarttime = datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, day=params['acq_date'].day, + hour=0, minute=0, second=0, microsecond=0, tzinfo=datetime.timezone.utc) if str(params['starttime']) == str(today_Starttime): for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, - second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond) + currTime = datetime.datetime(year=tr.stats.starttime.year, month=tr.stats.starttime.month, day=tr.stats.starttime.day, + hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, + second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond, tzinfo=datetime.timezone.utc) if currTime > maxStarttime: maxStarttime = currTime @@ -1172,17 +1243,19 @@

    Module sprit.sprit_hvsr

    today_Endtime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=23, minute=59, second=59, microsecond=999999)) - minEndtime = datetime.time(hour=23, minute=59, second=59, microsecond=999999) - if str(params['endtime']) == str(today_Endtime): + tomorrow_Endtime = today_Endtime + (60*60*24) + minEndtime = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)#(hour=23, minute=59, second=59, microsecond=999999) + if str(params['endtime']) == str(today_Endtime) or str(params['endtime'])==tomorrow_Endtime: for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, - second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond) + currTime = datetime.datetime(year=tr.stats.endtime.year, month=tr.stats.endtime.month, day=tr.stats.endtime.day, + hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, + second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond, tzinfo=datetime.timezone.utc) if currTime < minEndtime: minEndtime = currTime - newEndtime = obspy.UTCDateTime(datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, - day = params['acq_date'].day, + newEndtime = obspy.UTCDateTime(datetime.datetime(year=minEndtime.year, month=minEndtime.month, + day = minEndtime.day, hour=minEndtime.hour, minute=minEndtime.minute, - second=minEndtime.second, microsecond=minEndtime.microsecond)) + second=minEndtime.second, microsecond=minEndtime.microsecond, tzinfo=datetime.timezone.utc)) params['endtime'] = newEndtime params['params']['endtime'] = newEndtime @@ -1222,10 +1295,12 @@

    Module sprit.sprit_hvsr

    #Remerge data dataIN = dataIN.merge(method=1) + #Plot the input stream? if plot_input_stream: try: params['InputPlot'] = _plot_specgram_stream(stream=dataIN, params=params, component='Z', stack_type='linear', detrend='mean', dbscale=True, fill_gaps=None, ylimstd=3, return_fig=True, fig=None, ax=None, show_plot=False) - _get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + #_get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + plt.show() except: print('Error with default plotting method, falling back to internal obspy plotting method') dataIN.plot(method='full', linewidth=0.25) @@ -1236,6 +1311,7 @@

    Module sprit.sprit_hvsr

    else: dataIN = _sort_channels(input=dataIN, source=source, verbose=verbose) + #Clean up the ends of the data unless explicitly specified to do otherwise (this is a kwarg, not a parameter) if 'clean_ends' not in kwargs.keys(): clean_ends=True else: @@ -1322,6 +1398,8 @@

    Module sprit.sprit_hvsr

    ppsd_kwargs_sprit_defaults['skip_on_gaps'] = True if 'period_step_octaves' not in ppsd_kwargs: ppsd_kwargs_sprit_defaults['period_step_octaves'] = 0.03125 + if 'period_limits' not in ppsd_kwargs: + ppsd_kwargs_sprit_defaults['period_limits'] = [1/hvsr_data['hvsr_band'][1], 1/hvsr_data['hvsr_band'][0]] #Get Probablistic power spectral densities (PPSDs) #Get default args for function @@ -1335,7 +1413,6 @@

    Module sprit.sprit_hvsr

    ppsd_kwargs = get_default_args(PPSD) ppsd_kwargs.update(ppsd_kwargs_sprit_defaults)#Update with sprit defaults, or user input - orig_args['ppsd_kwargs'] = [ppsd_kwargs] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): @@ -1370,7 +1447,7 @@

    Module sprit.sprit_hvsr

    hvsr_data[site_name]['ProcessingStatus']['OverallStatus'] = False return hvsr_data else: - paz=hvsr_data['paz'] + paz = hvsr_data['paz'] stream = hvsr_data['stream'] #Get ppsds of e component @@ -1505,10 +1582,18 @@

    Module sprit.sprit_hvsr

    hvsrDF['TimesProcessed_MPLEnd'] = hvsrDF['TimesProcessed_MPL'] + (ppsd_kwargs['ppsd_length']/86400) hvsrDF['Use'] = True + hvsrDF['Use']=hvsrDF['Use'].astype(bool) for gap in hvsr_data['ppsds']['Z']['times_gaps']: - hvsrDF['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ - (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ - + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'].gt(gap[1].matplotlib_date))| \ + (hvsrDF['TimesProcessed_MPLEnd'].lt(gap[0].matplotlib_date))# | \ + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + if 'xwindows_out' in hvsr_data.keys(): + for window in hvsr_data['xwindows_out']: + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].lt(window[0]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].lt(window[0]) )| \ + (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].gt(window[1]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].gt(window[1])) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + hvsrDF.set_index('TimesProcessed', inplace=True) hvsr_data['hvsr_df'] = hvsrDF #Create dict entry to keep track of how many outlier hvsr curves are removed (2-item list with [0]=current number, [1]=original number of curves) @@ -1523,6 +1608,11 @@

    Module sprit.sprit_hvsr

    hvsr_data = sprit_utils.make_it_classy(hvsr_data) + hvsr_data['processing_parameters'] = {} + hvsr_data['processing_parameters']['generate_ppsds'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['generate_ppsds'][key] = value + hvsr_data['ProcessingStatus']['PPSDStatus'] = True hvsr_data = _check_processing_status(hvsr_data) return hvsr_data @@ -1549,14 +1639,57 @@

    Module sprit.sprit_hvsr

    params : dict Modified input dictionary with additional key:value pair containing paz dictionary (key = "paz") """ - invPath = params['metapath'] raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'trm', 't'] if params['instrument'].lower() in raspShakeInstNameList: if update_metadata: params = _update_shake_metadata(filepath=invPath, params=params, write_path=write_path) params = _read_RS_Metadata(params, source=source) + elif params['instrument'].lower() in trominoNameList: + params['paz'] = {'Z':{}, 'E':{}, 'N':{}} + #ALL THESE VALUES ARE PLACEHOLDERS, taken from RASPBERRY SHAKE! (Needed for PPSDs) + params['paz']['Z'] = {'sensitivity': 360000000.0, + 'gain': 360000000.0, + 'poles': [(-1+0j), (-3.03+0j), (-3.03+0j), (-666.67+0j)], + 'zeros': [0j, 0j, 0j]} + params['paz']['E'] = params['paz']['Z'] + params['paz']['N'] = params['paz']['Z'] + + channelObj_Z = obspy.core.inventory.channel.Channel(code='BHZ', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=90, types=None, external_references=None, + sample_rate=None, sample_rate_ratio_number_samples=None, sample_rate_ratio_number_seconds=None, + storage_format=None, clock_drift_in_seconds_per_sample=None, calibration_units=None, + calibration_units_description=None, sensor=None, pre_amplifier=None, data_logger=None, + equipments=None, response=None, description=None, comments=None, start_date=None, end_date=None, + restricted_status=None, alternate_code=None, historical_code=None, data_availability=None, + identifiers=None, water_level=None, source_id=None) + channelObj_E = obspy.core.inventory.channel.Channel(code='BHE', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=90, dip=0) + + channelObj_N = obspy.core.inventory.channel.Channel(code='BHN', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=0) + + siteObj = obspy.core.inventory.util.Site(name=params['params']['site'], description=None, town=None, county=None, region=None, country=None) + stationObj = obspy.core.inventory.station.Station(code='TZ', latitude=params['params']['latitude'], longitude=params['params']['longitude'], + elevation=params['params']['elevation'], channels=[channelObj_Z, channelObj_E, channelObj_N], site=siteObj, + vault=None, geology=None, equipments=None, operators=None, creation_date=datetime.datetime.today(), + termination_date=None, total_number_of_channels=None, + selected_number_of_channels=None, description='Estimated data for Tromino, this is NOT from the manufacturer', + comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, water_level=None, source_id=None) + + network = [obspy.core.inventory.network.Network(code='TROM', stations=[stationObj], total_number_of_stations=None, + selected_number_of_stations=None, description=None, comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, operators=None, source_id=None)] + + params['inv'] = obspy.Inventory(networks=network) else: if not invPath: pass #if invPath is None @@ -1618,6 +1751,10 @@

    Module sprit.sprit_hvsr

    #Curve pass? orig_args = locals().copy() #Get the initial arguments + hvsr_results['processing_parameters']['get_report'] = {} + for key, value in orig_args.items(): + hvsr_results['processing_parameters']['get_report'][key] = value + if (verbose and isinstance(hvsr_results, HVSRBatch)) or (verbose and not hvsr_results['batch']): if isinstance(hvsr_results, HVSRData) and hvsr_results['batch']: pass @@ -1687,8 +1824,8 @@

    Module sprit.sprit_hvsr

    curvePass = curvTestsPassed > 2 #Peak Pass? - peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ @@ -1823,24 +1960,25 @@

    Module sprit.sprit_hvsr

    report_string_list.append(internalSeparator) report_string_list.append('') + justSize=34 #Print individual results report_string_list.append('\tCurve Tests: {}/3 passed (3/3 needed)'.format(curvTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]} Length of processing windows: {hvsr_results['BestPeak']['Report']['Lw']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]} Number of significant cycles: {hvsr_results['BestPeak']['Report']['Nc']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]} Low StDev. of H/V Curve over time: {hvsr_results['BestPeak']['Report']['σ_A(f)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]}"+" Length of processing windows".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Lw']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]}"+" Number of significant cycles".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Nc']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]}"+" Small H/V StDev over time".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['σ_A(f)']}") report_string_list.append('') report_string_list.append("\tPeak Tests: {}/6 passed (5/6 needed)".format(peakTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]} Clarity Below Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f-)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]} Clarity Above Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f+)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]} Clarity of Peak Amplitude: {hvsr_results['BestPeak']['Report']['A0']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]}"+" Peak is prominent below".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f-)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]}"+" Peak is prominent above".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f+)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]}"+" Peak is large".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A0']}") if hvsr_results['BestPeak']['PassList']['FreqStability']: res = sprit_utils.check_mark() else: res = sprit_utils.x_mark() - report_string_list.append(f"\t\t {res} Stability of Peak Freq. Over time: {hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]} Stability of Peak (Freq. StDev): {hvsr_results['BestPeak']['Report']['Sf']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]} Stability of Peak (Amp. StDev): {hvsr_results['BestPeak']['Report']['Sa']}") + report_string_list.append(f"\t\t {res}"+ " Peak freq. is stable over time".ljust(justSize)+ f"{hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]}"+" Stability of peak (Freq. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sf']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]}"+" Stability of peak (Amp. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sa']}") report_string_list.append('') report_string_list.append(f"Calculated using {hvsr_results['hvsr_df']['Use'].sum()}/{hvsr_results['hvsr_df']['Use'].count()} time windows".rjust(sepLen-1)) report_string_list.append(extSiteSeparator) @@ -1864,7 +2002,7 @@

    Module sprit.sprit_hvsr

    import pandas as pd pdCols = ['Site Name', 'Acq_Date', 'Longitude', 'Latitide', 'Elevation', 'PeakFrequency', 'WindowLengthFreq.','SignificantCycles','LowCurveStDevOverTime', - 'PeakFreqClarityBelow','PeakFreqClarityAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] + 'PeakProminenceBelow','PeakProminenceAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] d = hvsr_results criteriaList = [] for p in hvsr_results['BestPeak']["PassList"]: @@ -1933,32 +2071,32 @@

    Module sprit.sprit_hvsr

    return hvsr_results #Main function for plotting results -def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, xtype='freq', fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs): +def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs): """Function to plot HVSR data Parameters ---------- hvsr_data : dict Dictionary containing output from process_hvsr function - plot_type : str='HVSR' or list + plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC' The plot_type of plot(s) to plot. If list, will plot all plots listed - 'HVSR' : Standard HVSR plot, including standard deviation - - '[HVSR] p' : HVSR plot with BestPeaks shown - - '[HVSR] p' : HVSR plot with best picked peak shown - - '[HVSR] p* all' : HVSR plot with all picked peaks shown - - '[HVSR] p* t' : HVSR plot with peaks from all time steps in background - - '[HVSR p* ann] : Annotates plot with peaks - - '[HVSR] -s' : HVSR plots don't show standard deviation - - '[HVSR] t' : HVSR plot with individual hv curves for each time step shown - - '[HVSR] c' : HVSR plot with each components' spectra. Recommended to do this last (or just before 'specgram'), since doing c+ can make the component chart its own chart - 'Specgram' : Combined spectrogram of all components - - '[spec]' : basic spectrogram plot of H/V curve + - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below: + - 'p' shows a vertical dotted line at frequency of the "best" peak + - 'ann' annotates the frequency value of of the "best" peak + - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) + - 't' shows the H/V curve for all time windows + -'tp' shows all the peaks from the H/V curves of all the time windows + - 'COMP' - plot of the PPSD curves for each individual component ("C" also works) + - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR) + - 'p' shows a vertical dotted line at frequency of the "best" peak + - 'ann' annotates the frequency value of of the "best" peak + - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) + - 't' shows the H/V curve for all time windows + - 'SPEC' - spectrogram style plot of the H/V curve over time + - 'p' shows a horizontal dotted line at the frequency of the "best" peak + - 'ann' annotates the frequency value of the "best" peak use_subplots : bool, default = True Whether to output the plots as subplots (True) or as separate plots (False) - xtype : str, default = 'freq' - String for what to use, between frequency or period - For frequency, the following are accepted (case does not matter): 'f', 'Hz', 'freq', 'frequency' - For period, the following are accepted (case does not matter): 'p', 'T', 's', 'sec', 'second', 'per', 'period' fig : matplotlib.Figure, default = None If not None, matplotlib figure on which plot is plotted ax : matplotlib.Axis, default = None @@ -2077,12 +2215,17 @@

    Module sprit.sprit_hvsr

    fig, axis = plt.subplots() if p == 'hvsr': - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax) + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) elif p=='comp': plotComponents[0] = plotComponents[0][:-1] - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax) + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) elif p=='spec': - _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False) + plottypeKwargs = {} + for c in plotComponents: + print(c) + plottypeKwargs[c] = True + kwargs.update(plottypeKwargs) + _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) else: warnings.warn('Plot type {p} not recognized', UserWarning) @@ -2137,6 +2280,7 @@

    Module sprit.sprit_hvsr

    metapath = '', hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], + instrument_settings=None, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -2187,6 +2331,10 @@

    Module sprit.sprit_hvsr

    Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. + instrument_settings : None, str, default=None + The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. + If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). + The default settings can be reset using the save_settings() function with the parameter settings_path='default'. verbose : bool, default=False Whether to print output and results to terminal @@ -2330,6 +2478,24 @@

    Module sprit.sprit_hvsr

    'ProcessingStatus':{'InputStatus':True, 'OverallStatus':True} } + #Replace any default parameter settings with those from json file of interest, potentially + if instrument_settings is None: + instrument_settings_dict = {} + elif instrument_settings == "default" or instrument_settings is True: + # Update inputParamDict with default file + default_settings_json = settings_dir.joinpath('instrument_settings.json') + with open(default_settings_json.as_posix(), 'r') as f: + instrument_settings_dict = json.load(f) + else: + # Update inputParamDict with file + with open(instrument_settings, 'r') as f: + instrument_settings_dict = json.load(f) + + for settingName in instrument_settings_dict.keys(): + if settingName in inputParamDict.keys(): + inputParamDict[settingName] = instrument_settings_dict[settingName] + + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True params = _check_processing_status(params) @@ -2585,10 +2751,8 @@

    Module sprit.sprit_hvsr

    xValMin = min(ppsds[k]['period_bin_centers']) xValMax = max(ppsds[k]['period_bin_centers']) - #Resample period bin values x_periods[k] = np.logspace(np.log10(xValMin), np.log10(xValMax), num=resample) - if smooth or type(smooth) is int: if smooth: smooth = 51 #Default smoothing window @@ -2616,7 +2780,6 @@

    Module sprit.sprit_hvsr

    #Get average psd value across time for each channel (used to calc main H/V curve) psdValsTAvg[k] = np.nanmean(np.array(psdRaw[k]), axis=0) x_freqs[k] = np.divide(np.ones_like(x_periods[k]), x_periods[k]) - stDev[k] = np.std(psdRaw[k], axis=0) stDevValsM[k] = np.array(psdValsTAvg[k] - stDev[k]) stDevValsP[k] = np.array(psdValsTAvg[k] + stDev[k]) @@ -2634,9 +2797,8 @@

    Module sprit.sprit_hvsr

    anyK = list(x_freqs.keys())[0] hvsr_curve, _ = __get_hvsr_curve(x=x_freqs[anyK], psd=psdValsTAvg, method=methodInt, hvsr_data=hvsr_data, verbose=verbose) origPPSD = hvsr_data['ppsds_obspy'].copy() - #Add some other variables to our output dictionary - hvsr_data = {'input_params':hvsr_data, + hvsr_dataUpdate = {'input_params':hvsr_data, 'x_freqs':x_freqs, 'hvsr_curve':hvsr_curve, 'x_period':x_periods, @@ -2653,7 +2815,7 @@

    Module sprit.sprit_hvsr

    'hvsr_df':hvsr_data['hvsr_df'] } - hvsr_out = HVSRData(hvsr_data) + hvsr_out = HVSRData(hvsr_dataUpdate) #This is if manual editing was used (should probably be updated at some point to just use masks) if 'xwindows_out' in hvsr_data.keys(): @@ -2727,7 +2889,6 @@

    Module sprit.sprit_hvsr

    bool_col='Use' eval_col='HV_Curves' - testCol = hvsr_out['hvsr_df'].loc[hvsr_out['hvsr_df'][bool_col], eval_col].apply(np.nanstd).gt((avg_stdT + (std_stdT * outlier_curve_std))) low_std_val = avg_stdT - (std_stdT * outlier_curve_std) hi_std_val = avg_stdT + (std_stdT * outlier_curve_std) @@ -2781,16 +2942,15 @@

    Module sprit.sprit_hvsr

    hvsr_out = __gethvsrparams(hvsr_out) #Include the original obspy stream in the output - #print(hvsr_data.keys()) - #print(type(hvsr_data)) - #print(hvsr_data['input_params'].keys()) - hvsr_out['input_stream'] = hvsr_data['input_params']['input_stream'] #input_stream - + hvsr_out['input_stream'] = hvsr_dataUpdate['input_params']['input_stream'] #input_stream hvsr_out = sprit_utils.make_it_classy(hvsr_out) - hvsr_out['ProcessingStatus']['HVStatus'] = True hvsr_out = _check_processing_status(hvsr_out) + hvsr_data['processing_parameters']['process_hvsr'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['process_hvsr'][key] = value + return hvsr_out #Function to remove noise windows from data @@ -2956,12 +3116,15 @@

    Module sprit.sprit_hvsr

    #Add output if isinstance(output, (HVSRData, dict)): - output['stream'] = outStream + if isinstance(outStream, (obspy.Stream, obspy.Trace)): + output['stream'] = outStream + else: + output['stream'] = outStream['stream'] output['input_stream'] = hvsr_data['input_stream'] output['ProcessingStatus']['RemoveNoiseStatus'] = True output = _check_processing_status(output) - if 'hvsr_df' in output.keys(): + if 'hvsr_df' in output.keys() or ('params' in output.keys() and 'hvsr_df' in output['params'].keys())or ('input_params' in output.keys() and 'hvsr_df' in output['input_params'].keys()): hvsrDF = output['hvsr_df'] outStream = output['stream'].split() @@ -2975,8 +3138,10 @@

    Module sprit.sprit_hvsr

    if trEndTime < trStartTime and comp_end==comp_start: gap = [trEndTime,trStartTime] + output['hvsr_df']['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ + output['hvsr_df']['Use'] = output['hvsr_df']['Use'].astype(bool) trEndTime = trace.stats.endtime @@ -2987,8 +3152,10 @@

    Module sprit.sprit_hvsr

    else: warnings.warn(f"Output of type {type(output)} for this function will likely result in errors in other processing steps. Returning hvsr_data data.") return hvsr_data - - + + output = sprit_utils.make_it_classy(output) + if 'xwindows_out' not in output.keys(): + output['xwindows_out'] = [] return output #Remove outlier ppsds @@ -3053,16 +3220,19 @@

    Module sprit.sprit_hvsr

    psds_to_rid.append(i) #Use dataframe - hvsrDF = params['hvsr_df'] + hvsrDF = params['hvsr_df'].copy() psdVals = hvsrDF['psd_values_'+k] - params['hvsr_df'][k+'_CurveMedian'] = psdVals.apply(np.nanmedian) - params['hvsr_df'][k+'_CurveMean'] = psdVals.apply(np.nanmean) - - totMean = np.nanmean(params['hvsr_df'][k+'_CurveMean']) - stds[k] = np.nanstd(params['hvsr_df'][k+'_CurveMean']) + hvsrDF[k+'_CurveMedian'] = psdVals.apply(np.nanmedian) + hvsrDF[k+'_CurveMean'] = psdVals.apply(np.nanmean) - meanArr = params['hvsr_df'][k+'_CurveMean'] - params['hvsr_df']['Use'] = meanArr < (totMean + outlier_std * stds[k]) + totMean = np.nanmean(hvsrDF[k+'_CurveMean']) + stds[k] = np.nanstd(hvsrDF[k+'_CurveMean']) + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + #meanArr = hvsrDF[k+'_CurveMean'].loc[hvsrDF['Use']] + threshVal = totMean + outlier_std * stds[k] + hvsrDF['Use'] = hvsrDF[k+'_CurveMean'][hvsrDF['Use']].lt(threshVal) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) psds_to_rid = np.unique(psds_to_rid) @@ -3073,6 +3243,83 @@

    Module sprit.sprit_hvsr

    params['ppsds'][k]['current_times_used'] = np.delete(params['ppsds'][k]['current_times_used'], index, axis=0) return params +###WORKING ON THIS +#Save default instrument and processing settings to json file(s) +def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False): + """Save settings to json file + + Parameters + ---------- + settings_path : str, default="default" + Where to save the json file(s) containing the settings, by default 'default'. + If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. + If 'all' is selected, a directory should be supplied. + Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory. + settings_type : str, {'all', 'instrument', 'processing'} + What kind of settings to save. + If 'all', saves all possible types in their respective json files. + If 'instrument', save the instrument settings to their respective file. + If 'processing', saves the processing settings to their respective file. By default 'all' + verbose : bool, default=True + Whether to print outputs and information to the terminal + + """ + fnameDict = {} + fnameDict['instrument'] = "instrument_settings.json" + fnameDict['processing'] = "processing_settings.json" + + if settings_path == 'default' or settings_path is True: + settingsPath = resource_dir.joinpath('settings') + else: + settings_path = pathlib.Path(settings_path) + if not settings_path.exists(): + if not settings_path.parent.exists(): + print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}') + settingsPath = pathlib.Path.home() + else: + settingsPath = settings_path.parent + + if settings_path.is_dir(): + settingsPath = settings_path + elif settings_path.is_file(): + settingsPath = settings_path.parent + fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json" + fnameDict['processing'] = settings_path.name+"_processingSettings.json" + + #Get final filepaths + instSetFPath = settingsPath.joinpath(fnameDict['instrument']) + procSetFPath = settingsPath.joinpath(fnameDict['processing']) + + #Get settings values + instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"] + procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report] + + instrument_settings_dict = {} + processing_settings_dict = {} + + for k in instKeys: + if isinstance(hvsr_data[k], pathlib.PurePath): + instrument_settings_dict[k] = hvsr_data[k].as_posix() + else: + instrument_settings_dict[k] = hvsr_data[k] + + for func in procFuncs: + funcName = func.__name__ + processing_settings_dict[funcName] = {} + for arg in inspect.getfullargspec(func)[0]: + if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)): + pass + else: + processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg] + + #Save settings files + if settings_type.lower()=='instrument' or settings_type.lower()=='all': + with open(instSetFPath.as_posix(), 'w') as instSetF: + json.dump(instrument_settings_dict, instSetF) + if settings_type.lower()=='processing' or settings_type.lower()=='all': + with open(procSetFPath.as_posix(), 'w') as procSetF: + json.dump(processing_settings_dict, procSetF) + #Read data as batch def batch_data_read(input_data, batch_type='table', param_col=None, batch_params=None, verbose=False, **readcsv_getMeta_fetch_kwargs): """Function to read data in data as a batch of multiple data files. This is best used through sprit.fetch_data(*args, source='batch', **other_kwargs). @@ -3931,6 +4178,88 @@

    Module sprit.sprit_hvsr

    return rawDataIN +#Read data from Tromino +def __read_tromino_files(datapath, params, verbose=False): + """Function to read data from tromino. Specifically, this has been lightly tested on Tromino 3G+ machines + + Parameters + ---------- + datapath : str, pathlib.Path() + The input parameter _datapath_ from sprit.input_params() + params : HVSRData or HVSRBatch + The parameters as read in from input_params() and and fetch_data() + verbose : bool, optional + Whether to print results to terminal, by default False + + Returns + ------- + obspy.Stream + An obspy.Stream object containing the trace data from the Tromino instrument + """ + dPath = datapath + + strucSizes = {'c':1, 'b':1,'B':1, '?':1, + 'h':2,'H':2,'e':2, + 'i':4,'I':4,'l':4,'L':4,'f':4, + 'q':8,'Q':8,'d':8, + 'n':8,'N':8,'s':16,'p':16,'P':16,'x':16} + + #H (pretty sure it's Q) I L or Q all seem to work (probably not Q?) + structFormat = 'H' + structSize = strucSizes[structFormat] + + dataList = [] + with open(dPath, 'rb') as f: + while True: + data = f.read(structSize) # Read 4 bytes + if not data: # End of file + break + value = struct.unpack(structFormat, data)[0] # Interpret as a float + dataList.append(value) + + import numpy as np + dataArr = np.array(dataList) + import matplotlib.pyplot as plt + + medVal = np.nanmedian(dataArr[50000:100000]) + + startByte=24576 + comp1 = dataArr[startByte::3] - medVal + comp2 = dataArr[startByte+1::3] - medVal + comp3 = dataArr[startByte+2::3] - medVal + headerBytes = dataArr[:startByte] + + #fig, ax = plt.subplots(3, sharex=True, sharey=True) + #ax[0].plot(comp1, linewidth=0.1, c='k') + #ax[1].plot(comp2, linewidth=0.1, c='k') + #ax[2].plot(comp3, linewidth=0.1, c='k') + + sTime = obspy.UTCDateTime(params['acq_date'].year, params['acq_date'].month, params['acq_date'].day, + params['starttime'].hour, params['starttime'].minute, + params['starttime'].second,params['starttime'].microsecond) + eTime = sTime + (((len(comp1))/128)/60)*60 + + traceHeader1 = {'sampling_rate':128, + 'calib' : 1, + 'npts':len(comp1), + 'network':'AM', + 'location':'00', + 'station' : 'TRMNO', + 'channel':'BHE', + 'starttime':sTime} + + traceHeader2=traceHeader1.copy() + traceHeader3=traceHeader1.copy() + traceHeader2['channel'] = 'BHN' + traceHeader3['channel'] = 'BHZ' + + trace1 = obspy.Trace(data=comp1, header=traceHeader1) + trace2 = obspy.Trace(data=comp2, header=traceHeader2) + trace3 = obspy.Trace(data=comp3, header=traceHeader3) + + st = obspy.Stream([trace1, trace2, trace3]) + return st + ##Helper functions for remove_noise() #Helper function for removing gaps def __remove_gaps(stream, window_gaps_obspy): @@ -4464,13 +4793,13 @@

    Module sprit.sprit_hvsr

    if 'hvsr_curve' in input.keys(): fig, ax = plot_hvsr(hvsr_data=input, plot_type='spec', returnfig=True, cmap='turbo') else: - params = input.copy() - input = input['stream'] + hvsr_data = input#.copy() + input_stream = hvsr_data['stream'] - if isinstance(input, obspy.core.stream.Stream): - fig, ax = _plot_specgram_stream(input, component=['Z']) - elif isinstance(input, obspy.core.trace.Trace): - fig, ax = _plot_specgram_stream(input) + if isinstance(input_stream, obspy.core.stream.Stream): + fig, ax = _plot_specgram_stream(input_stream, component=['Z']) + elif isinstance(input_stream, obspy.core.trace.Trace): + fig, ax = _plot_specgram_stream(input_stream) global lineArtist global winArtist @@ -4494,10 +4823,10 @@

    Module sprit.sprit_hvsr

    fig.canvas.mpl_connect('close_event', _on_fig_close)#(clickNo, xWindows, pathList, windowDrawn, winArtist, lineArtist, x0, fig, ax)) plt.pause(1) - params['xwindows_out'] = xWindows - params['fig'] = fig - params['ax'] = ax - return params + hvsr_data['xwindows_out'] = xWindows + hvsr_data['fig_noise'] = fig + hvsr_data['ax_noise'] = ax + return hvsr_data #Support function to help select_windows run properly def _on_fig_close(event): @@ -5233,7 +5562,6 @@

    Module sprit.sprit_hvsr

    ax.set_ylabel('H/V Ratio'+'\n['+hvsr_data['method']+']') ax.set_title(hvsr_data['input_params']['site']) - #print("label='comp'" in str(ax.__dict__['_axes'])) for k in plot_type: if k=='p' and 'all' not in plot_type: @@ -5286,7 +5614,7 @@

    Module sprit.sprit_hvsr

    plotSuff = plotSuff+'IndComponents_' if 'c' not in plot_type[0]:#This is part of the hvsr axis - fig.tight_layout() + #fig.tight_layout() axis2 = ax.twinx() compAxis = axis2 #axis2 = plt.gca() @@ -5356,7 +5684,7 @@

    Module sprit.sprit_hvsr

    axisbox.append(float(i)) if kwargs['show_legend']: - ax.legend(loc=legendLoc) + ax.legend(loc=legendLoc,bbox_to_anchor=(1.05, 1)) __plot_current_fig(save_dir=save_dir, filename=filename, @@ -5389,18 +5717,28 @@

    Module sprit.sprit_hvsr

    def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix='',**kwargs): """Private function for plotting average spectrogram of all three channels from ppsds """ + # Get all input parameters if fig is None and ax is None: fig, ax = plt.subplots() + print(kwargs.keys()) if 'kwargs' in kwargs.keys(): kwargs = kwargs['kwargs'] - if 'peak_plot' in kwargs.keys(): + if 'spec' in kwargs.keys(): + del kwargs['spec'] + + if 'p' in kwargs.keys(): peak_plot=True - del kwargs['peak_plot'] + del kwargs['p'] else: peak_plot=False - + + if 'ann' in kwargs.keys(): + annotate=True + del kwargs['ann'] + else: + annotate=False if 'grid' in kwargs.keys(): ax.grid(which=kwargs['grid'], alpha=0.25) @@ -5433,25 +5771,22 @@

    Module sprit.sprit_hvsr

    else: kwargs['cmap'] = 'turbo' + # Setup ppsds = hvsr_data['ppsds']#[k]['current_times_used'] import matplotlib.dates as mdates anyKey = list(ppsds.keys())[0] - - psdHList =[] - psdZList =[] - for k in hvsr_data['psd_raw']: - if 'z' in k.lower(): - psdZList.append(hvsr_data['psd_raw'][k]) - else: - psdHList.append(hvsr_data['psd_raw'][k]) - #if detrend: - # psdArr = np.subtract(psdArr, np.median(psdArr, axis=0)) - psdArr = hvsr_data['ind_hvsr_curves'].T + # Get data + psdArr = np.stack(hvsr_data['hvsr_df']['HV_Curves'].apply(np.flip)) + useArr = np.array(hvsr_data['hvsr_df']['Use']) + useArr = np.tile(useArr, (psdArr.shape[1], 1)).astype(int) + useArr = np.clip(useArr, a_min=0.15, a_max=1) - xmin = min(hvsr_data['ppsds'][anyKey]['current_times_used'][:-1]).matplotlib_date - xmax = max(hvsr_data['ppsds'][anyKey]['current_times_used'][:-1]).matplotlib_date - + # Get times + xmin = hvsr_data['hvsr_df']['TimesProcessed_MPL'].min() + xmax = hvsr_data['hvsr_df']['TimesProcessed_MPL'].max() + + #Format times tTicks = mdates.MinuteLocator(byminute=range(0,60,5)) ax.xaxis.set_major_locator(tTicks) tTicks_minor = mdates.SecondLocator(bysecond=[0]) @@ -5461,58 +5796,60 @@

    Module sprit.sprit_hvsr

    ax.xaxis.set_major_formatter(tLabels) ax.tick_params(axis='x', labelsize=8) - if hvsr_data['ppsds'][anyKey]['current_times_used'][0].date != hvsr_data['ppsds'][anyKey]['current_times_used'][-1].date: - day = str(hvsr_data['ppsds'][anyKey]['current_times_used'][0].date)+' - '+str(hvsr_data['ppsds'][anyKey]['current_times_used'][1].date) + #Get day label for bottom of chart + if hvsr_data['hvsr_df'].index[0].date() != hvsr_data['hvsr_df'].index[-1].date(): + day = str(hvsr_data['hvsr_df'].index[0].date())+' - '+str(hvsr_data['hvsr_df'].index[-1].date()) else: - day = str(hvsr_data['ppsds'][anyKey]['current_times_used'][0].date) + day = str(hvsr_data['hvsr_df'].index[0].date()) + #Get extents ymin = hvsr_data['input_params']['hvsr_band'][0] ymax = hvsr_data['input_params']['hvsr_band'][1] - extList = [xmin, xmax, ymin, ymax] - - #ax = plt.gca() - #fig = plt.gcf() - freqticks = np.flip(hvsr_data['x_freqs'][anyKey]) yminind = np.argmin(np.abs(ymin-freqticks)) ymaxind = np.argmin(np.abs(ymax-freqticks)) freqticks = freqticks[yminind:ymaxind] + freqticks = np.logspace(np.log10(freqticks[0]), np.log10(freqticks[-1]), num=999) + + extList = [xmin, xmax, ymin, ymax] + + #Set up axes + ax.set_facecolor([0,0,0]) #Create black background for transparency to look darker + + # Interpolate into linear + new_indices = np.linspace(freqticks[0], freqticks[-1], len(freqticks)) + linList = [] + for row in psdArr: + row = row.astype(np.float16) + linList.append(np.interp(new_indices, freqticks, row)) + linear_arr = np.stack(linList) + + # Create chart + im = ax.imshow(linear_arr.T, origin='lower', extent=extList, aspect='auto', alpha=useArr, **kwargs) + ax.tick_params(left=True, right=True, top=True) - #Set up axes, since data is already in semilog - axy = ax.twinx() - axy.set_yticks([]) - axy.zorder=0 - ax.zorder=1 - ax.set_facecolor('#ffffff00') #Create transparent background for front axis - #plt.sca(axy) - im = ax.imshow(psdArr, origin='lower', extent=extList, aspect='auto', interpolation='nearest', **kwargs) - ax.tick_params(left=False, right=False) - #plt.sca(ax) if peak_plot: - ax.hlines(hvsr_data['BestPeak']['f0'], xmin, xmax, colors='k', linestyles='dashed', alpha=0.5) + ax.axhline(hvsr_data['BestPeak']['f0'], c='k', linestyle='dotted', zorder=1000) + + if annotate: + xLocation = float(xmin) + (float(xmax)-float(xmin))*0.98 + ann = ax.text(x=xLocation, y=float(hvsr_data['BestPeak']['f0'])+0.3, fontsize='small', s=f"{hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va='bottom', + bbox={'alpha':0.8, 'edgecolor':'w', 'fc':'w', 'pad':0.3}) - #FreqTicks =np.arange(1,np.round(max(hvsr_data['x_freqs'][anyKey]),0), 10) - specTitle = ax.set_title(hvsr_data['input_params']['site']+': Spectrogram') - bgClr = (fig.get_facecolor()[0], fig.get_facecolor()[1], fig.get_facecolor()[2], 0.1) - specTitle.set_color(bgClr) ax.set_xlabel('UTC Time \n'+day) - if colorbar: - cbar = plt.colorbar(mappable=im) + cbar = plt.colorbar(mappable=im, orientation='horizontal') cbar.set_label('H/V Ratio') ax.set_ylabel(ylabel) - ax.set_yticks(freqticks) - ax.semilogy() - ax.set_ylim(hvsr_data['input_params']['hvsr_band']) + ax.set_yscale('log') - #fig.tight_layout() + #plt.sca(ax) #plt.rcParams['figure.dpi'] = 500 #plt.rcParams['figure.figsize'] = (12,4) fig.canvas.draw() - #fig.tight_layout() - #plt.show() + return fig, ax #Plot spectrogram from stream @@ -5831,6 +6168,7 @@

    Module sprit.sprit_hvsr

    window_num = np.array(hvsr_data['psd_raw'][anyKey]).shape[0] for _i in range(len(_peak)): + # Test 1 peakFreq= _peak[_i]['f0'] test1 = peakFreq > 10/window_len @@ -5840,38 +6178,37 @@

    Module sprit.sprit_hvsr

    halfF0 = peakFreq/2 doublef0 = peakFreq*2 + test3 = True failCount = 0 for i, freq in enumerate(hvsr_data['x_freqs'][anyKey][:-1]): - ###IS THIS RIGHT??? if freq >= halfF0 and freq <doublef0: + compVal = 2 if peakFreq >= 0.5: - if hvsr_data['hvsr_log_std'][i] >= 2: + if hvsr_data['hvsr_log_std'][i] >= compVal: test3=False failCount +=1 + else: #if peak freq is less than 0.5 - if hvsr_data['hvsr_log_std'][i] >= 3: + compVal = 3 + if hvsr_data['hvsr_log_std'][i] >= compVal: test3=False failCount +=1 if test1: - _peak[_i]['Report']['Lw'] = '{} > 10 / {} {}'.format(round(peakFreq,3), int(window_len), sprit_utils.check_mark()) + _peak[_i]['Report']['Lw'] = f'{round(peakFreq,3)} > {10/int(window_len):0.3} (10 / {int(window_len)}) {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['Lw'] = '{} > 10 / {} {}'.format(round(peakFreq,3), int(window_len), '✘') + _peak[_i]['Report']['Lw'] = f'{round(peakFreq,3)} > {10/int(window_len):0.3} (10 / {int(window_len)}) {sprit_utils.x_mark()}' if test2: - _peak[_i]['Report']['Nc'] = '{} > 200 {}'.format(round(nc,0), sprit_utils.check_mark()) + _peak[_i]['Report']['Nc'] = f'{int(nc)} > 200 {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['Nc'] = '{} > 200 {}'.format(round(nc,0), '✘') + _peak[_i]['Report']['Nc'] = f'{int(nc)} > 200 {sprit_utils.x_mark()}' if test3: - if peakFreq >= 0.5: - compVal = 2 - else: - compVal = 3 - _peak[_i]['Report']['σ_A(f)'] = 'σ_A for all freqs {}-{} < {} {}'.format(round(peakFreq*0.5, 3), round(peakFreq*2, 3), compVal, sprit_utils.check_mark()) + _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['σ_A(f)'] = 'σ_A for all freqs {}-{} < {} {}'.format(round(peakFreq*0.5, 3), round(peakFreq*2, 3), compVal, '✘') + _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.x_mark()}' _peak[_i]['PassList']['WindowLengthFreq.'] = test1 _peak[_i]['PassList']['SignificantCycles'] = test2 @@ -5917,16 +6254,16 @@

    Module sprit.sprit_hvsr

    for _i in range(len(_peak)): #Initialize as False - _peak[_i]['f-'] = '✘' - _peak[_i]['Report']['A(f-)'] = 'No A_h/v in freqs {}-{} < {} {}'.format(round(_peak[_i]['A0']/4, 3), round(_peak[_i]['A0'], 3), round(_peak[_i]['A0']/2, 3), '✘') - _peak[_i]['PassList']['PeakFreqClarityBelow'] = False #Start with assumption that it is False until we find an instance where it is True + _peak[_i]['f-'] = sprit_utils.x_mark() + _peak[_i]['Report']['A(f-)'] = f"H/V curve > {_peak[_i]['A0']/2:0.2f} for all {_peak[_i]['A0']/4:0.2f} Hz-{_peak[_i]['A0']:0.3f} Hz {sprit_utils.x_mark()}" + _peak[_i]['PassList']['PeakProminenceBelow'] = False #Start with assumption that it is False until we find an instance where it is True for _j in range(jstart, -1, -1): # There exist one frequency f-, lying between f0/4 and f0, such that A0 / A(f-) > 2. if (float(_peak[_i]['f0']) / 4.0 <= _x[_j] < float(_peak[_i]['f0'])) and float(_peak[_i]['A0']) / _y[_j] > 2.0: _peak[_i]['Score'] += 1 _peak[_i]['f-'] = '%10.3f %1s' % (_x[_j], sprit_utils.check_mark()) - _peak[_i]['Report']['A(f-)'] = 'A({}): {} < {} {}'.format(round(_x[_j], 3), round(_y[_j], 3), round(_peak[_i]['A0']/2,3), sprit_utils.check_mark()) - _peak[_i]['PassList']['PeakFreqClarityBelow'] = True + _peak[_i]['Report']['A(f-)'] = f"Amp. of H/V Curve @{_x[_j]:0.3f}Hz ({_y[_j]:0.3f}) < {_peak[_i]['A0']/2:0.3f} (peak amp. {_peak[_i]['A0']:0.3f}) {sprit_utils.check_mark()}" + _peak[_i]['PassList']['PeakProminenceBelow'] = True break else: pass @@ -5935,25 +6272,21 @@

    Module sprit.sprit_hvsr

    max_rank += 1 for _i in range(len(_peak)): #Initialize as False - _peak[_i]['f+'] = '✘' - _peak[_i]['Report']['A(f+)'] = 'No A_h/v in freqs {}-{} < {} {}'.format(round(_peak[_i]['A0'], 3), round(_peak[_i]['A0']*4, 3), round(_peak[_i]['A0']/2, 3), '✘') - _peak[_i]['PassList']['PeakFreqClarityAbove'] = False + _peak[_i]['f+'] = sprit_utils.x_mark() + _peak[_i]['Report']['A(f+)'] = f"H/V curve > {_peak[_i]['A0']/2:0.2f} for all {_peak[_i]['A0']:0.2f} Hz-{_peak[_i]['A0']*4:0.3f} Hz {sprit_utils.x_mark()}" + _peak[_i]['PassList']['PeakProminenceAbove'] = False for _j in range(len(_x) - 1): # There exist one frequency f+, lying between f0 and 4*f0, such that A0 / A(f+) > 2. if float(_peak[_i]['f0']) * 4.0 >= _x[_j] > float(_peak[_i]['f0']) and \ float(_peak[_i]['A0']) / _y[_j] > 2.0: _peak[_i]['Score'] += 1 - _peak[_i]['f+'] = '%10.3f %1s' % (_x[_j], sprit_utils.check_mark()) - _peak[_i]['Report']['A(f+)'] = 'A({}): {} < {} {}'.format(round(_x[_j], 3), round(_y[_j], 3), round(_peak[_i]['A0']/2,3), sprit_utils.check_mark()) - _peak[_i]['PassList']['PeakFreqClarityAbove'] = True + _peak[_i]['f+'] = f"{_x[_j]:0.3f} {sprit_utils.check_mark()}" + _peak[_i]['Report']['A(f+)'] = f"H/V Curve at {_x[_j]:0.2f} Hz: {_y[_j]:0.2f} < {_peak[_i]['A0']/2:0.2f} (f0/2) {sprit_utils.check_mark()}" + _peak[_i]['PassList']['PeakProminenceAbove'] = True break else: pass -# if False in clarityPass: -# _peak[_i]['PassList']['PeakFreqClarityBelow'] = False -# else: -# _peak[_i]['PassList']['PeakFreqClarityAbove'] = True #Amplitude Clarity test # Only peaks with A0 > 2 pass @@ -5963,11 +6296,11 @@

    Module sprit.sprit_hvsr

    for _i in range(len(_peak)): if float(_peak[_i]['A0']) > _a0: - _peak[_i]['Report']['A0'] = '%10.2f > %0.1f %1s' % (_peak[_i]['A0'], _a0, sprit_utils.check_mark()) + _peak[_i]['Report']['A0'] = f"Amplitude of peak ({_peak[_i]['A0']:0.2f}) > {int(_a0)} {sprit_utils.check_mark()}" _peak[_i]['Score'] += 1 _peak[_i]['PassList']['PeakAmpClarity'] = True else: - _peak[_i]['Report']['A0'] = '%10.2f > %0.1f %1s' % (_peak[_i]['A0'], _a0, '✘') + _peak[_i]['Report']['A0'] = '%0.2f > %0.1f %1s' % (_peak[_i]['A0'], _a0, sprit_utils.x_mark()) _peak[_i]['PassList']['PeakAmpClarity'] = False return _peak @@ -6004,19 +6337,17 @@

    Module sprit.sprit_hvsr

    for _i in range(len(_peak)): _dx = 1000000. _found_m.append(False) - _peak[_i]['Report']['P-'] = '✘' + _peak[_i]['Report']['P-'] = sprit_utils.x_mark() for _j in range(len(_peakm)): if abs(_peakm[_j]['f0'] - _peak[_i]['f0']) < _dx: _index = _j _dx = abs(_peakm[_j]['f0'] - _peak[_i]['f0']) if _peak[_i]['f0'] * 0.95 <= _peakm[_j]['f0'] <= _peak[_i]['f0'] * 1.05: - _peak[_i]['Report']['P-'] = '%0.3f within ±5%s of %0.3f %1s' % (_peakm[_j]['f0'], '%', - _peak[_i]['f0'], sprit_utils.check_mark()) + _peak[_i]['Report']['P-'] = f"{_peakm[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.check_mark()}" _found_m[_i] = True break - if _peak[_i]['Report']['P-'] == '✘': - _peak[_i]['Report']['P-'] = '%0.3f within ±5%s of %0.3f %1s' % (_peakm[_j]['f0'], '%', ##changed i to j - _peak[_i]['f0'], '✘') + if _peak[_i]['Report']['P-'] == sprit_utils.x_mark(): + _peak[_i]['Report']['P-'] = f"{_peakm[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" # Then Check above _found_p = list() @@ -6030,25 +6361,21 @@

    Module sprit.sprit_hvsr

    _dx = abs(_peakp[_j]['f0'] - _peak[_i]['f0']) if _peak[_i]['f0'] * 0.95 <= _peakp[_j]['f0'] <= _peak[_i]['f0'] * 1.05: if _found_m[_i]: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % ( - _peakp[_j]['f0'], '%', _peak[_i]['f0'], sprit_utils.check_mark()) + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.check_mark()}" _peak[_i]['Score'] += 1 _peak[_i]['PassList']['FreqStability'] = True else: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % ( - _peakp[_j]['f0'], '%', _peak[_i]['f0'], '✘') + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" _peak[_i]['PassList']['FreqStability'] = False break else: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % (_peakp[_j]['f0'], '%', _peak[_i]['f0'], '✘') + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" _peak[_i]['PassList']['FreqStability'] = False if _peak[_i]['Report']['P+'] == sprit_utils.x_mark() and len(_peakp) > 0: - _peak[_i]['Report']['P+'] = '%0.3f within ±5%s of %0.3f %1s' % ( - _peakp[_j]['f0'], '%', _peak[_i]['f0'], sprit_utils.x_mark()) #changed i to j + _peak[_i]['Report']['P+'] = f"{_peakp[_j]['f0']:0.2f} Hz within ±5% of {_peak[_i]['f0']:0.2f} Hz {sprit_utils.x_mark()}" return _peak - # Check stability def __check_stability(_stdf, _peak, _hvsr_log_std, rank): """Test peaks for satisfying stability conditions as outlined by SESAME 2004 @@ -6089,104 +6416,96 @@

    Module sprit.sprit_hvsr

    if _this_peak['f0'] < 0.2: _e = 0.25 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True - else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.48 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, - sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif 0.2 <= _this_peak['f0'] < 0.5: _e = 0.2 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.40 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, - sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif 0.5 <= _this_peak['f0'] < 1.0: _e = 0.15 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.3 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif 1.0 <= _this_peak['f0'] <= 2.0: _e = 0.1 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s ' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.25 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_AmpStD'] = False elif _this_peak['f0'] > 0.2: _e = 0.05 if _stdf[_i] < _e * _this_peak['f0']: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], - sprit_utils.check_mark()) + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_FreqStD'] = True else: - _peak[_i]['Report']['Sf'] = '%10.4f < %0.2f * %0.3f %1s' % (_stdf[_i], _e, _this_peak['f0'], '✘') + _peak[_i]['Report']['Sf'] = f"St.Dev. of Peak Freq. ({_stdf[_i]:0.2f}) < {(_e * _this_peak['f0']):0.3f} {sprit_utils.x_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False _t = 0.2 if _hvsr_log_std[_i] < _t: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, sprit_utils.check_mark()) + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['Score'] += 1 _this_peak['PassList']['PeakStability_AmpStD'] = True else: - _peak[_i]['Report']['Sa'] = '%10.4f < %0.2f %1s' % (_hvsr_log_std[_i], _t, '✘') + _peak[_i]['Report']['Sa'] = f"St.Dev. of Peak Amp. ({_hvsr_log_std[_i]:0.3f}) < {_t:0.2f} {sprit_utils.check_mark()}" _this_peak['PassList']['PeakStability_FreqStD'] = False return _peak @@ -6498,7 +6817,7 @@

    Raises

    -def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose=False) +def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_range=[1, 20], verbose=False)

    Function to run tests on HVSR peaks to find best one and see if it passes quality checks

    @@ -6508,6 +6827,10 @@

    Parameters

    Dictionary containing all the calculated information about the HVSR data (i.e., hvsr_out returned from process_hvsr)
    hvsr_band : tuple or list, default=[0.4, 40]
    2-item tuple or list with lower and upper limit of frequencies to analyze
    +
    peak_selection : str or numeric, default='max'
    +
    How to select the "best" peak used in the analysis. For peak_selection="max" (default value), the highest peak within peak_freq_range is used. +For peak_selection='scored', an algorithm is used to select the peak based in part on which peak passes the most SESAME criteria. +If a numeric value is used (e.g., int or float), this should be a frequency value to manually select as the peak of interest.
    peak_freq_range : tuple or list, default=[1, 20];
    The frequency range within which to check for peaks. If there is an HVSR curve with multiple peaks, this allows the full range of data to be processed while limiting peak picks to likely range.
    verbose : bool, default=False
    @@ -6523,7 +6846,7 @@

    Returns

    Expand source code -
    def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_freq_range=[1, 20], verbose=False):
    +
    def check_peaks(hvsr_data, hvsr_band=[0.4, 40], peak_selection='max', peak_freq_range=[1, 20], verbose=False):
         """Function to run tests on HVSR peaks to find best one and see if it passes quality checks
     
             Parameters
    @@ -6532,6 +6855,10 @@ 

    Returns

    Dictionary containing all the calculated information about the HVSR data (i.e., hvsr_out returned from process_hvsr) hvsr_band : tuple or list, default=[0.4, 40] 2-item tuple or list with lower and upper limit of frequencies to analyze + peak_selection : str or numeric, default='max' + How to select the "best" peak used in the analysis. For peak_selection="max" (default value), the highest peak within peak_freq_range is used. + For peak_selection='scored', an algorithm is used to select the peak based in part on which peak passes the most SESAME criteria. + If a numeric value is used (e.g., int or float), this should be a frequency value to manually select as the peak of interest. peak_freq_range : tuple or list, default=[1, 20]; The frequency range within which to check for peaks. If there is an HVSR curve with multiple peaks, this allows the full range of data to be processed while limiting peak picks to likely range. verbose : bool, default=False @@ -6544,6 +6871,10 @@

    Returns

    """ orig_args = locals().copy() #Get the initial arguments + hvsr_data['processing_parameters']['check_peaks'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['check_peaks'][key] = value + if (verbose and 'input_params' not in hvsr_data.keys()) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: pass @@ -6579,13 +6910,38 @@

    Returns

    if hvsr_data['ProcessingStatus']['OverallStatus']: if not hvsr_band: hvsr_band = [0.4,40] + hvsr_data['hvsr_band'] = hvsr_band anyK = list(hvsr_data['x_freqs'].keys())[0] x = hvsr_data['x_freqs'][anyK] #Consistent for all curves y = hvsr_data['hvsr_curve'] #Calculated based on "Use" column - index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + + scorelist = ['score', 'scored', 'best', 's'] + maxlist = ['max', 'highest', 'm'] + # Convert peak_selection to numeric, get index of nearest value as list item for __init_peaks() + try: + peak_val = float(peak_selection) + index_list = [np.argmin(np.abs(x - peak_val))] + except: + # If score method is being used, get index list for __init_peaks() + if peak_selection in scorelist: + index_list = hvsr_data['hvsr_peak_indices'] #Calculated based on hvsr_curve + elif peak_selection in maxlist: + #Get max index as item in list for __init_peaks() + startInd = np.argmin(np.abs(x - peak_freq_range[0])) + endInd = np.argmin(np.abs(x - peak_freq_range[1])) + if startInd > endInd: + holder = startInd + startInd = endInd + endInd = holder + subArrayMax = np.argmax(y[startInd:endInd]) + + # If max val is in subarray, this will be the same as the max of curve + # Otherwise, it will be the index of the value that is max within peak_freq_range + index_list = [subArrayMax+startInd] + hvsrp = hvsr_data['hvsrp'] #Calculated based on "Use" column hvsrm = hvsr_data['hvsrm'] #Calculated based on "Use" column @@ -6611,7 +6967,7 @@

    Returns

    peakp = __init_peaks(x, hvsrp, index_p, hvsr_band, peak_freq_range) peakp = __check_clarity(x, hvsrp, peakp, do_rank=True) - #Do for hvsrm + # Do for hvsrm # Find the relative extrema of hvsrm (hvsr - 1 standard deviation) if not np.isnan(np.sum(hvsrm)): index_m = __find_peaks(hvsrm) @@ -6621,6 +6977,7 @@

    Returns

    peakm = __init_peaks(x, hvsrm, index_m, hvsr_band, peak_freq_range) peakm = __check_clarity(x, hvsrm, peakm, do_rank=True) + # Get standard deviation of time peaks stdf = __get_stdf(x, index_list, hvsrPeaks) peak = __check_freq_stability(peak, peakm, peakp) @@ -6632,7 +6989,7 @@

    Returns

    # Get the BestPeak based on the peak score # Calculate whether each peak passes enough tests curveTests = ['WindowLengthFreq.','SignificantCycles', 'LowCurveStDevOverTime'] - peakTests = ['PeakFreqClarityBelow', 'PeakFreqClarityAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] + peakTests = ['PeakProminenceBelow', 'PeakProminenceAbove', 'PeakAmpClarity', 'FreqStability', 'PeakStability_FreqStD', 'PeakStability_AmpStD'] bestPeakScore = 0 for p in hvsr_data['PeakReport']: @@ -6666,6 +7023,10 @@

    Returns

    else: hvsr_data['BestPeak'] = {} print(f"Processing Errors: No Best Peak identified for {hvsr_data['site']}") + try: + hvsr_data.plot() + except: + pass return hvsr_data
    @@ -6789,7 +7150,9 @@

    Parameters

    date=params['acq_date'] #Cleanup for gui input - if '}' in str(params['datapath']): + if isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass + elif '}' in str(params['datapath']): params['datapath'] = params['datapath'].as_posix().replace('{','') params['datapath'] = params['datapath'].split('}') @@ -6802,7 +7165,10 @@

    Parameters

    #Make sure datapath is pointing to an actual file if isinstance(params['datapath'],list): for i, d in enumerate(params['datapath']): - params['datapath'][i] = sprit_utils.checkifpath(str(d).strip()) + params['datapath'][i] = sprit_utils.checkifpath(str(d).strip(), sample_list=sampleList) + dPath = params['datapath'] + elif isinstance(params['datapath'], (obspy.Stream, obspy.Trace)): + pass else: dPath = sprit_utils.checkifpath(params['datapath'], sample_list=sampleList) @@ -6854,15 +7220,26 @@

    Parameters

    #Select which instrument we are reading from (requires different processes for each instrument) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'tromino 3g', 'tromino 3g+', 'tr', 't'] + + #Get any kwargs that are included in obspy.read + obspyReadKwargs = {} + for argName in inspect.getfullargspec(obspy.read)[0]: + if argName in kwargs.keys(): + obspyReadKwargs[argName] = kwargs[argName] #Select how reading will be done if source=='raw': - if inst.lower() in raspShakeInstNameList: - try: + try: + if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) - except: - raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") - return params + + elif inst.lower() in trominoNameList: + rawDataIN = __read_tromino_files(dPath, params, verbose=verbose) + except: + raise RuntimeError(f"Data not fetched for {params['site']}. Check input parameters or the data file.") + elif source=='stream' or isinstance(params, (obspy.Stream, obspy.Trace)): + rawDataIN = params['datapath'].copy() elif source=='dir': if inst.lower() in raspShakeInstNameList: rawDataIN = __read_RS_file_struct(dPath, source, year, doy, inv, params, verbose=verbose) @@ -6873,28 +7250,27 @@

    Parameters

    for f in temp_file_glob: currParams = params currParams['datapath'] = f + curr_data = fetch_data(params, source='file', #all the same as input, except just reading the one file using the source='file' - trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs), + trim_dir=trim_dir, export_format=export_format, detrend=detrend, detrend_order=detrend_order, update_metadata=update_metadata, verbose=verbose, **kwargs) + curr_data.merge() obspyFiles[f.stem] = curr_data #Add path object to dict, with filepath's stem as the site name return HVSRBatch(obspyFiles) - elif source=='file' and str(params['datapath']).lower() not in sampleList: if isinstance(dPath, list) or isinstance(dPath, tuple): rawStreams = [] for datafile in dPath: - rawStream = obspy.read(datafile) + rawStream = obspy.read(datafile, **obspyReadKwargs) rawStreams.append(rawStream) #These are actually streams, not traces - for i, stream in enumerate(rawStreams): if i == 0: rawDataIN = obspy.Stream(stream) #Just in case else: rawDataIN = rawDataIN + stream #This adds a stream/trace to the current stream object - elif str(dPath)[:6].lower()=='sample': pass else: - rawDataIN = obspy.read(dPath)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) + rawDataIN = obspy.read(dPath, **obspyReadKwargs)#, starttime=obspy.core.UTCDateTime(params['starttime']), endttime=obspy.core.UTCDateTime(params['endtime']), nearest_sample =True) import warnings with warnings.catch_warnings(): warnings.simplefilter(action='ignore', category=UserWarning) @@ -6941,12 +7317,15 @@

    Parameters

    except: RuntimeError(f'source={source} not recognized, and datapath cannot be read using obspy.read()') + #Get metadata from the data itself, if not reading raw data try: dataIN = rawDataIN.copy() if source!='raw': #Use metadata from file for; # site if params['site'] == "HVSR Site": + if isinstance(dPath, (list, tuple)): + dPath = dPath[0] params['site'] = dPath.stem params['params']['site'] = dPath.stem @@ -6983,11 +7362,13 @@

    Parameters

    today_Starttime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=0, minute=0, second=0, microsecond=0)) - maxStarttime = datetime.time(hour=0, minute=0, second=0, microsecond=0) + maxStarttime = datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, day=params['acq_date'].day, + hour=0, minute=0, second=0, microsecond=0, tzinfo=datetime.timezone.utc) if str(params['starttime']) == str(today_Starttime): for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, - second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond) + currTime = datetime.datetime(year=tr.stats.starttime.year, month=tr.stats.starttime.month, day=tr.stats.starttime.day, + hour=tr.stats.starttime.hour, minute=tr.stats.starttime.minute, + second=tr.stats.starttime.second, microsecond=tr.stats.starttime.microsecond, tzinfo=datetime.timezone.utc) if currTime > maxStarttime: maxStarttime = currTime @@ -7002,17 +7383,19 @@

    Parameters

    today_Endtime = obspy.UTCDateTime(datetime.datetime(year=datetime.date.today().year, month=datetime.date.today().month, day = datetime.date.today().day, hour=23, minute=59, second=59, microsecond=999999)) - minEndtime = datetime.time(hour=23, minute=59, second=59, microsecond=999999) - if str(params['endtime']) == str(today_Endtime): + tomorrow_Endtime = today_Endtime + (60*60*24) + minEndtime = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)#(hour=23, minute=59, second=59, microsecond=999999) + if str(params['endtime']) == str(today_Endtime) or str(params['endtime'])==tomorrow_Endtime: for tr in dataIN.merge(): - currTime = datetime.time(hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, - second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond) + currTime = datetime.datetime(year=tr.stats.endtime.year, month=tr.stats.endtime.month, day=tr.stats.endtime.day, + hour=tr.stats.endtime.hour, minute=tr.stats.endtime.minute, + second=tr.stats.endtime.second, microsecond=tr.stats.endtime.microsecond, tzinfo=datetime.timezone.utc) if currTime < minEndtime: minEndtime = currTime - newEndtime = obspy.UTCDateTime(datetime.datetime(year=params['acq_date'].year, month=params['acq_date'].month, - day = params['acq_date'].day, + newEndtime = obspy.UTCDateTime(datetime.datetime(year=minEndtime.year, month=minEndtime.month, + day = minEndtime.day, hour=minEndtime.hour, minute=minEndtime.minute, - second=minEndtime.second, microsecond=minEndtime.microsecond)) + second=minEndtime.second, microsecond=minEndtime.microsecond, tzinfo=datetime.timezone.utc)) params['endtime'] = newEndtime params['params']['endtime'] = newEndtime @@ -7052,10 +7435,12 @@

    Parameters

    #Remerge data dataIN = dataIN.merge(method=1) + #Plot the input stream? if plot_input_stream: try: params['InputPlot'] = _plot_specgram_stream(stream=dataIN, params=params, component='Z', stack_type='linear', detrend='mean', dbscale=True, fill_gaps=None, ylimstd=3, return_fig=True, fig=None, ax=None, show_plot=False) - _get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + #_get_removed_windows(input=dataIN, fig=params['InputPlot'][0], ax=params['InputPlot'][1], lineArtist =[], winArtist = [], existing_lineArtists=[], existing_xWindows=[], exist_win_format='matplotlib', keep_line_artists=True, time_type='matplotlib', show_plot=True) + plt.show() except: print('Error with default plotting method, falling back to internal obspy plotting method') dataIN.plot(method='full', linewidth=0.25) @@ -7066,6 +7451,7 @@

    Parameters

    else: dataIN = _sort_channels(input=dataIN, source=source, verbose=verbose) + #Clean up the ends of the data unless explicitly specified to do otherwise (this is a kwarg, not a parameter) if 'clean_ends' not in kwargs.keys(): clean_ends=True else: @@ -7185,6 +7571,8 @@

    Returns

    ppsd_kwargs_sprit_defaults['skip_on_gaps'] = True if 'period_step_octaves' not in ppsd_kwargs: ppsd_kwargs_sprit_defaults['period_step_octaves'] = 0.03125 + if 'period_limits' not in ppsd_kwargs: + ppsd_kwargs_sprit_defaults['period_limits'] = [1/hvsr_data['hvsr_band'][1], 1/hvsr_data['hvsr_band'][0]] #Get Probablistic power spectral densities (PPSDs) #Get default args for function @@ -7198,7 +7586,6 @@

    Returns

    ppsd_kwargs = get_default_args(PPSD) ppsd_kwargs.update(ppsd_kwargs_sprit_defaults)#Update with sprit defaults, or user input - orig_args['ppsd_kwargs'] = [ppsd_kwargs] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): @@ -7233,7 +7620,7 @@

    Returns

    hvsr_data[site_name]['ProcessingStatus']['OverallStatus'] = False return hvsr_data else: - paz=hvsr_data['paz'] + paz = hvsr_data['paz'] stream = hvsr_data['stream'] #Get ppsds of e component @@ -7368,10 +7755,18 @@

    Returns

    hvsrDF['TimesProcessed_MPLEnd'] = hvsrDF['TimesProcessed_MPL'] + (ppsd_kwargs['ppsd_length']/86400) hvsrDF['Use'] = True + hvsrDF['Use']=hvsrDF['Use'].astype(bool) for gap in hvsr_data['ppsds']['Z']['times_gaps']: - hvsrDF['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ - (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ - + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'].gt(gap[1].matplotlib_date))| \ + (hvsrDF['TimesProcessed_MPLEnd'].lt(gap[0].matplotlib_date))# | \ + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + if 'xwindows_out' in hvsr_data.keys(): + for window in hvsr_data['xwindows_out']: + hvsrDF['Use'] = (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].lt(window[0]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].lt(window[0]) )| \ + (hvsrDF['TimesProcessed_MPL'][hvsrDF['Use']].gt(window[1]) & hvsrDF['TimesProcessed_MPLEnd'][hvsrDF['Use']].gt(window[1])) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + hvsrDF.set_index('TimesProcessed', inplace=True) hvsr_data['hvsr_df'] = hvsrDF #Create dict entry to keep track of how many outlier hvsr curves are removed (2-item list with [0]=current number, [1]=original number of curves) @@ -7386,6 +7781,11 @@

    Returns

    hvsr_data = sprit_utils.make_it_classy(hvsr_data) + hvsr_data['processing_parameters'] = {} + hvsr_data['processing_parameters']['generate_ppsds'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['generate_ppsds'][key] = value + hvsr_data['ProcessingStatus']['PPSDStatus'] = True hvsr_data = _check_processing_status(hvsr_data) return hvsr_data
    @@ -7439,14 +7839,57 @@

    Returns

    params : dict Modified input dictionary with additional key:value pair containing paz dictionary (key = "paz") """ - invPath = params['metapath'] raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] + trominoNameList = ['tromino', 'trom', 'trm', 't'] if params['instrument'].lower() in raspShakeInstNameList: if update_metadata: params = _update_shake_metadata(filepath=invPath, params=params, write_path=write_path) params = _read_RS_Metadata(params, source=source) + elif params['instrument'].lower() in trominoNameList: + params['paz'] = {'Z':{}, 'E':{}, 'N':{}} + #ALL THESE VALUES ARE PLACEHOLDERS, taken from RASPBERRY SHAKE! (Needed for PPSDs) + params['paz']['Z'] = {'sensitivity': 360000000.0, + 'gain': 360000000.0, + 'poles': [(-1+0j), (-3.03+0j), (-3.03+0j), (-666.67+0j)], + 'zeros': [0j, 0j, 0j]} + params['paz']['E'] = params['paz']['Z'] + params['paz']['N'] = params['paz']['Z'] + + channelObj_Z = obspy.core.inventory.channel.Channel(code='BHZ', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=90, types=None, external_references=None, + sample_rate=None, sample_rate_ratio_number_samples=None, sample_rate_ratio_number_seconds=None, + storage_format=None, clock_drift_in_seconds_per_sample=None, calibration_units=None, + calibration_units_description=None, sensor=None, pre_amplifier=None, data_logger=None, + equipments=None, response=None, description=None, comments=None, start_date=None, end_date=None, + restricted_status=None, alternate_code=None, historical_code=None, data_availability=None, + identifiers=None, water_level=None, source_id=None) + channelObj_E = obspy.core.inventory.channel.Channel(code='BHE', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=90, dip=0) + + channelObj_N = obspy.core.inventory.channel.Channel(code='BHN', location_code='00', latitude=params['params']['latitude'], + longitude=params['params']['longitude'], elevation=params['params']['elevation'], depth=params['params']['depth'], + azimuth=0, dip=0) + + siteObj = obspy.core.inventory.util.Site(name=params['params']['site'], description=None, town=None, county=None, region=None, country=None) + stationObj = obspy.core.inventory.station.Station(code='TZ', latitude=params['params']['latitude'], longitude=params['params']['longitude'], + elevation=params['params']['elevation'], channels=[channelObj_Z, channelObj_E, channelObj_N], site=siteObj, + vault=None, geology=None, equipments=None, operators=None, creation_date=datetime.datetime.today(), + termination_date=None, total_number_of_channels=None, + selected_number_of_channels=None, description='Estimated data for Tromino, this is NOT from the manufacturer', + comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, water_level=None, source_id=None) + + network = [obspy.core.inventory.network.Network(code='TROM', stations=[stationObj], total_number_of_stations=None, + selected_number_of_stations=None, description=None, comments=None, start_date=None, + end_date=None, restricted_status=None, alternate_code=None, historical_code=None, + data_availability=None, identifiers=None, operators=None, source_id=None)] + + params['inv'] = obspy.Inventory(networks=network) else: if not invPath: pass #if invPath is None @@ -7552,6 +7995,10 @@

    Returns

    #Curve pass? orig_args = locals().copy() #Get the initial arguments + hvsr_results['processing_parameters']['get_report'] = {} + for key, value in orig_args.items(): + hvsr_results['processing_parameters']['get_report'][key] = value + if (verbose and isinstance(hvsr_results, HVSRBatch)) or (verbose and not hvsr_results['batch']): if isinstance(hvsr_results, HVSRData) and hvsr_results['batch']: pass @@ -7621,8 +8068,8 @@

    Returns

    curvePass = curvTestsPassed > 2 #Peak Pass? - peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakFreqClarityBelow'] + - hvsr_results['BestPeak']['PassList']['PeakFreqClarityAbove']+ + peakTestsPassed = ( hvsr_results['BestPeak']['PassList']['PeakProminenceBelow'] + + hvsr_results['BestPeak']['PassList']['PeakProminenceAbove']+ hvsr_results['BestPeak']['PassList']['PeakAmpClarity']+ hvsr_results['BestPeak']['PassList']['FreqStability']+ hvsr_results['BestPeak']['PassList']['PeakStability_FreqStD']+ @@ -7757,24 +8204,25 @@

    Returns

    report_string_list.append(internalSeparator) report_string_list.append('') + justSize=34 #Print individual results report_string_list.append('\tCurve Tests: {}/3 passed (3/3 needed)'.format(curvTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]} Length of processing windows: {hvsr_results['BestPeak']['Report']['Lw']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]} Number of significant cycles: {hvsr_results['BestPeak']['Report']['Nc']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]} Low StDev. of H/V Curve over time: {hvsr_results['BestPeak']['Report']['σ_A(f)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Lw'][-1]}"+" Length of processing windows".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Lw']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Nc'][-1]}"+" Number of significant cycles".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Nc']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['σ_A(f)'][-1]}"+" Small H/V StDev over time".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['σ_A(f)']}") report_string_list.append('') report_string_list.append("\tPeak Tests: {}/6 passed (5/6 needed)".format(peakTestsPassed)) - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]} Clarity Below Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f-)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]} Clarity Above Peak Frequency: {hvsr_results['BestPeak']['Report']['A(f+)']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]} Clarity of Peak Amplitude: {hvsr_results['BestPeak']['Report']['A0']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f-)'][-1]}"+" Peak is prominent below".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f-)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A(f+)'][-1]}"+" Peak is prominent above".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A(f+)']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['A0'][-1]}"+" Peak is large".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['A0']}") if hvsr_results['BestPeak']['PassList']['FreqStability']: res = sprit_utils.check_mark() else: res = sprit_utils.x_mark() - report_string_list.append(f"\t\t {res} Stability of Peak Freq. Over time: {hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]} Stability of Peak (Freq. StDev): {hvsr_results['BestPeak']['Report']['Sf']}") - report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]} Stability of Peak (Amp. StDev): {hvsr_results['BestPeak']['Report']['Sa']}") + report_string_list.append(f"\t\t {res}"+ " Peak freq. is stable over time".ljust(justSize)+ f"{hvsr_results['BestPeak']['Report']['P-'][:5]} and {hvsr_results['BestPeak']['Report']['P+'][:-1]} {res}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sf'][-1]}"+" Stability of peak (Freq. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sf']}") + report_string_list.append(f"\t\t {hvsr_results['BestPeak']['Report']['Sa'][-1]}"+" Stability of peak (Amp. StDev)".ljust(justSize)+f"{hvsr_results['BestPeak']['Report']['Sa']}") report_string_list.append('') report_string_list.append(f"Calculated using {hvsr_results['hvsr_df']['Use'].sum()}/{hvsr_results['hvsr_df']['Use'].count()} time windows".rjust(sepLen-1)) report_string_list.append(extSiteSeparator) @@ -7798,7 +8246,7 @@

    Returns

    import pandas as pd pdCols = ['Site Name', 'Acq_Date', 'Longitude', 'Latitide', 'Elevation', 'PeakFrequency', 'WindowLengthFreq.','SignificantCycles','LowCurveStDevOverTime', - 'PeakFreqClarityBelow','PeakFreqClarityAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] + 'PeakProminenceBelow','PeakProminenceAbove','PeakAmpClarity','FreqStability', 'PeakStability_FreqStD','PeakStability_AmpStD', 'PeakPasses'] d = hvsr_results criteriaList = [] for p in hvsr_results['BestPeak']["PassList"]: @@ -7966,7 +8414,7 @@

    Returns

    -def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-19', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath='', hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], verbose=False) +def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-28', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath='', hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], instrument_settings=None, verbose=False)

    Function for designating input parameters for reading in and processing data

    @@ -8016,6 +8464,10 @@

    Parameters

    Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later.
    peak_freq_range : list or tuple, default=[0.4, 40]
    Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range.
    +
    instrument_settings : None, str, default=None
    +
    The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. +If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). +The default settings can be reset using the save_settings() function with the parameter settings_path='default'.
    verbose : bool, default=False
    Whether to print output and results to terminal
    @@ -8049,6 +8501,7 @@

    Returns

    metapath = '', hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], + instrument_settings=None, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -8099,6 +8552,10 @@

    Returns

    Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. + instrument_settings : None, str, default=None + The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. + If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). + The default settings can be reset using the save_settings() function with the parameter settings_path='default'. verbose : bool, default=False Whether to print output and results to terminal @@ -8242,6 +8699,24 @@

    Returns

    'ProcessingStatus':{'InputStatus':True, 'OverallStatus':True} } + #Replace any default parameter settings with those from json file of interest, potentially + if instrument_settings is None: + instrument_settings_dict = {} + elif instrument_settings == "default" or instrument_settings is True: + # Update inputParamDict with default file + default_settings_json = settings_dir.joinpath('instrument_settings.json') + with open(default_settings_json.as_posix(), 'r') as f: + instrument_settings_dict = json.load(f) + else: + # Update inputParamDict with file + with open(instrument_settings, 'r') as f: + instrument_settings_dict = json.load(f) + + for settingName in instrument_settings_dict.keys(): + if settingName in inputParamDict.keys(): + inputParamDict[settingName] = instrument_settings_dict[settingName] + + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True params = _check_processing_status(params) @@ -8249,7 +8724,7 @@

    Returns

    -def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, xtype='freq', fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True, **kwargs) +def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True, **kwargs)

    Function to plot HVSR data

    @@ -8258,29 +8733,25 @@

    Parameters

    hvsr_data : dict
    Dictionary containing output from process_hvsr function
    -
    plot_type : str='HVSR' or list -
    +
    plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC'
    The plot_type of plot(s) to plot. If list, will plot all plots listed -'HVSR' : Standard HVSR plot, including standard deviation -- '[HVSR] p' : HVSR plot with BestPeaks shown -- '[HVSR] p' : HVSR plot with best picked peak shown -
    -- '[HVSR] p all' : HVSR plot with all picked peaks shown -
    -- '[HVSR] p
    t' : HVSR plot with peaks from all time steps in background -
    -- '[HVSR p* ann] : Annotates plot with peaks -- '[HVSR] -s' : HVSR plots don't show standard deviation -- '[HVSR] t' : HVSR plot with individual hv curves for each time step shown -- '[HVSR] c' : HVSR plot with each components' spectra. Recommended to do this last (or just before 'specgram'), since doing c+ can make the component chart its own chart -'Specgram' : Combined spectrogram of all components -- '[spec]' : basic spectrogram plot of H/V curve
    +- 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below: +- 'p' shows a vertical dotted line at frequency of the "best" peak +- 'ann' annotates the frequency value of of the "best" peak +- 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) +- 't' shows the H/V curve for all time windows +-'tp' shows all the peaks from the H/V curves of all the time windows +- 'COMP' - plot of the PPSD curves for each individual component ("C" also works) +- '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR) +- 'p' shows a vertical dotted line at frequency of the "best" peak +- 'ann' annotates the frequency value of of the "best" peak +- 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) +- 't' shows the H/V curve for all time windows +- 'SPEC' - spectrogram style plot of the H/V curve over time +- 'p' shows a horizontal dotted line at the frequency of the "best" peak +- 'ann' annotates the frequency value of the "best" peak
    use_subplots : bool, default = True
    Whether to output the plots as subplots (True) or as separate plots (False)
    -
    xtype : str, default = 'freq'
    -
    String for what to use, between frequency or period -For frequency, the following are accepted (case does not matter): 'f', 'Hz', 'freq', 'frequency' -For period, the following are accepted (case does not matter): 'p', 'T', 's', 'sec', 'second', 'per', 'period'
    fig : matplotlib.Figure, default = None
    If not None, matplotlib figure on which plot is plotted
    ax : matplotlib.Axis, default = None
    @@ -8311,32 +8782,32 @@

    Returns

    Expand source code -
    def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, xtype='freq', fig=None, ax=None, return_fig=False,  save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs):
    +
    def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False,  save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs):
         """Function to plot HVSR data
     
         Parameters
         ----------
         hvsr_data : dict                  
             Dictionary containing output from process_hvsr function
    -    plot_type : str='HVSR' or list    
    +    plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC'
             The plot_type of plot(s) to plot. If list, will plot all plots listed
    -        'HVSR' : Standard HVSR plot, including standard deviation
    -        - '[HVSR] p' : HVSR plot with BestPeaks shown
    -        - '[HVSR] p' : HVSR plot with best picked peak shown                
    -        - '[HVSR] p* all' : HVSR plot with all picked peaks shown                
    -        - '[HVSR] p* t' : HVSR plot with peaks from all time steps in background                
    -        - '[HVSR p* ann] : Annotates plot with peaks
    -        - '[HVSR] -s' : HVSR plots don't show standard deviation
    -        - '[HVSR] t' : HVSR plot with individual hv curves for each time step shown
    -        - '[HVSR] c' : HVSR plot with each components' spectra. Recommended to do this last (or just before 'specgram'), since doing c+ can make the component chart its own chart
    -        'Specgram' : Combined spectrogram of all components
    -        - '[spec]' : basic spectrogram plot of H/V curve
    +        - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below:
    +            - 'p' shows a vertical dotted line at frequency of the "best" peak
    +            - 'ann' annotates the frequency value of of the "best" peak
    +            - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified)
    +            - 't' shows the H/V curve for all time windows
    +                -'tp' shows all the peaks from the H/V curves of all the time windows
    +        - 'COMP' - plot of the PPSD curves for each individual component ("C" also works)
    +            - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR)
    +            - 'p' shows a vertical dotted line at frequency of the "best" peak
    +            - 'ann' annotates the frequency value of of the "best" peak
    +            - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified)
    +            - 't' shows the H/V curve for all time windows
    +        - 'SPEC' - spectrogram style plot of the H/V curve over time
    +            - 'p' shows a horizontal dotted line at the frequency of the "best" peak
    +            - 'ann' annotates the frequency value of the "best" peak
         use_subplots : bool, default = True
             Whether to output the plots as subplots (True) or as separate plots (False)
    -    xtype : str, default = 'freq'    
    -        String for what to use, between frequency or period
    -            For frequency, the following are accepted (case does not matter): 'f', 'Hz', 'freq', 'frequency'
    -            For period, the following are accepted (case does not matter): 'p', 'T', 's', 'sec', 'second', 'per', 'period'
         fig : matplotlib.Figure, default = None
             If not None, matplotlib figure on which plot is plotted
         ax : matplotlib.Axis, default = None
    @@ -8455,12 +8926,17 @@ 

    Returns

    fig, axis = plt.subplots() if p == 'hvsr': - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax) + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) elif p=='comp': plotComponents[0] = plotComponents[0][:-1] - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax) + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) elif p=='spec': - _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False) + plottypeKwargs = {} + for c in plotComponents: + print(c) + plottypeKwargs[c] = True + kwargs.update(plottypeKwargs) + _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) else: warnings.warn('Plot type {p} not recognized', UserWarning) @@ -8810,10 +9286,8 @@

    Returns

    xValMin = min(ppsds[k]['period_bin_centers']) xValMax = max(ppsds[k]['period_bin_centers']) - #Resample period bin values x_periods[k] = np.logspace(np.log10(xValMin), np.log10(xValMax), num=resample) - if smooth or type(smooth) is int: if smooth: smooth = 51 #Default smoothing window @@ -8841,7 +9315,6 @@

    Returns

    #Get average psd value across time for each channel (used to calc main H/V curve) psdValsTAvg[k] = np.nanmean(np.array(psdRaw[k]), axis=0) x_freqs[k] = np.divide(np.ones_like(x_periods[k]), x_periods[k]) - stDev[k] = np.std(psdRaw[k], axis=0) stDevValsM[k] = np.array(psdValsTAvg[k] - stDev[k]) stDevValsP[k] = np.array(psdValsTAvg[k] + stDev[k]) @@ -8859,9 +9332,8 @@

    Returns

    anyK = list(x_freqs.keys())[0] hvsr_curve, _ = __get_hvsr_curve(x=x_freqs[anyK], psd=psdValsTAvg, method=methodInt, hvsr_data=hvsr_data, verbose=verbose) origPPSD = hvsr_data['ppsds_obspy'].copy() - #Add some other variables to our output dictionary - hvsr_data = {'input_params':hvsr_data, + hvsr_dataUpdate = {'input_params':hvsr_data, 'x_freqs':x_freqs, 'hvsr_curve':hvsr_curve, 'x_period':x_periods, @@ -8878,7 +9350,7 @@

    Returns

    'hvsr_df':hvsr_data['hvsr_df'] } - hvsr_out = HVSRData(hvsr_data) + hvsr_out = HVSRData(hvsr_dataUpdate) #This is if manual editing was used (should probably be updated at some point to just use masks) if 'xwindows_out' in hvsr_data.keys(): @@ -8952,7 +9424,6 @@

    Returns

    bool_col='Use' eval_col='HV_Curves' - testCol = hvsr_out['hvsr_df'].loc[hvsr_out['hvsr_df'][bool_col], eval_col].apply(np.nanstd).gt((avg_stdT + (std_stdT * outlier_curve_std))) low_std_val = avg_stdT - (std_stdT * outlier_curve_std) hi_std_val = avg_stdT + (std_stdT * outlier_curve_std) @@ -9006,16 +9477,15 @@

    Returns

    hvsr_out = __gethvsrparams(hvsr_out) #Include the original obspy stream in the output - #print(hvsr_data.keys()) - #print(type(hvsr_data)) - #print(hvsr_data['input_params'].keys()) - hvsr_out['input_stream'] = hvsr_data['input_params']['input_stream'] #input_stream - + hvsr_out['input_stream'] = hvsr_dataUpdate['input_params']['input_stream'] #input_stream hvsr_out = sprit_utils.make_it_classy(hvsr_out) - hvsr_out['ProcessingStatus']['HVStatus'] = True hvsr_out = _check_processing_status(hvsr_out) + hvsr_data['processing_parameters']['process_hvsr'] = {} + for key, value in orig_args.items(): + hvsr_data['processing_parameters']['process_hvsr'][key] = value + return hvsr_out
    @@ -9231,12 +9701,15 @@

    Returns

    #Add output if isinstance(output, (HVSRData, dict)): - output['stream'] = outStream + if isinstance(outStream, (obspy.Stream, obspy.Trace)): + output['stream'] = outStream + else: + output['stream'] = outStream['stream'] output['input_stream'] = hvsr_data['input_stream'] output['ProcessingStatus']['RemoveNoiseStatus'] = True output = _check_processing_status(output) - if 'hvsr_df' in output.keys(): + if 'hvsr_df' in output.keys() or ('params' in output.keys() and 'hvsr_df' in output['params'].keys())or ('input_params' in output.keys() and 'hvsr_df' in output['input_params'].keys()): hvsrDF = output['hvsr_df'] outStream = output['stream'].split() @@ -9250,8 +9723,10 @@

    Returns

    if trEndTime < trStartTime and comp_end==comp_start: gap = [trEndTime,trStartTime] + output['hvsr_df']['Use'] = (hvsrDF['TimesProcessed_Obspy'].gt(gap[0]) & hvsrDF['TimesProcessed_Obspy'].gt(gap[1]) )| \ (hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[0]) & hvsrDF['TimesProcessed_ObspyEnd'].lt(gap[1]))# | \ + output['hvsr_df']['Use'] = output['hvsr_df']['Use'].astype(bool) trEndTime = trace.stats.endtime @@ -9262,8 +9737,10 @@

    Returns

    else: warnings.warn(f"Output of type {type(output)} for this function will likely result in errors in other processing steps. Returning hvsr_data data.") return hvsr_data - - + + output = sprit_utils.make_it_classy(output) + if 'xwindows_out' not in output.keys(): + output['xwindows_out'] = [] return output
    @@ -9354,16 +9831,19 @@

    Returns

    psds_to_rid.append(i) #Use dataframe - hvsrDF = params['hvsr_df'] + hvsrDF = params['hvsr_df'].copy() psdVals = hvsrDF['psd_values_'+k] - params['hvsr_df'][k+'_CurveMedian'] = psdVals.apply(np.nanmedian) - params['hvsr_df'][k+'_CurveMean'] = psdVals.apply(np.nanmean) + hvsrDF[k+'_CurveMedian'] = psdVals.apply(np.nanmedian) + hvsrDF[k+'_CurveMean'] = psdVals.apply(np.nanmean) - totMean = np.nanmean(params['hvsr_df'][k+'_CurveMean']) - stds[k] = np.nanstd(params['hvsr_df'][k+'_CurveMean']) - - meanArr = params['hvsr_df'][k+'_CurveMean'] - params['hvsr_df']['Use'] = meanArr < (totMean + outlier_std * stds[k]) + totMean = np.nanmean(hvsrDF[k+'_CurveMean']) + stds[k] = np.nanstd(hvsrDF[k+'_CurveMean']) + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + #meanArr = hvsrDF[k+'_CurveMean'].loc[hvsrDF['Use']] + threshVal = totMean + outlier_std * stds[k] + hvsrDF['Use'] = hvsrDF[k+'_CurveMean'][hvsrDF['Use']].lt(threshVal) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) psds_to_rid = np.unique(psds_to_rid) @@ -9470,8 +9950,14 @@

    Raises

    RuntimeError If the data being processed is a single file, an error will be raised if generate_ppsds() does not work correctly. No errors are raised for remove_noise() errors (since that is an optional step) and the process_hvsr() step (since that is the last processing step) . """ + if 'hvsr_band' not in kwargs.keys(): + kwargs['hvsr_band'] = inspect.signature(input_params).parameters['hvsr_band'].default + if 'peak_freq_range' not in kwargs.keys(): + kwargs['peak_freq_range'] = inspect.signature(input_params).parameters['peak_freq_range'].default + #Get the input parameters - input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in input_params.__code__.co_varnames} + input_params_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(input_params).parameters.keys())} + try: params = input_params(datapath=datapath, verbose=verbose, **input_params_kwargs) except: @@ -9484,15 +9970,14 @@

    Raises

    #Fetch Data try: - fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in fetch_data.__code__.co_varnames} + fetch_data_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(fetch_data).parameters.keys())} dataIN = fetch_data(params=params, source=source, verbose=verbose, **fetch_data_kwargs) except: #Even if batch, this is reading in data for all sites so we want to raise error, not just warn raise RuntimeError('Data not read correctly, see sprit.fetch_data() function and parameters for more details.') - #Remove Noise try: - remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in remove_noise.__code__.co_varnames} + remove_noise_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(remove_noise).parameters.keys())} data_noiseRemoved = remove_noise(hvsr_data=dataIN, verbose=verbose,**remove_noise_kwargs) except: data_noiseRemoved = dataIN @@ -9516,8 +10001,8 @@

    Raises

    #Generate PPSDs try: - generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in generate_ppsds.__code__.co_varnames} - PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in PPSD.__init__.__code__.co_varnames} + generate_ppsds_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(generate_ppsds).parameters.keys())} + PPSDkwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(PPSD).parameters.keys())} generate_ppsds_kwargs.update(PPSDkwargs) ppsd_data = generate_ppsds(hvsr_data=data_noiseRemoved, verbose=verbose,**generate_ppsds_kwargs) except Exception as e: @@ -9543,7 +10028,7 @@

    Raises

    #Process HVSR Curves try: - process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in process_hvsr.__code__.co_varnames} + process_hvsr_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(process_hvsr).parameters.keys())} hvsr_results = process_hvsr(hvsr_data=ppsd_data, verbose=verbose,**process_hvsr_kwargs) except Exception as e: traceback.print_exception(sys.exc_info()[1]) @@ -9573,10 +10058,10 @@

    Raises

    #Final post-processing/reporting #Check peaks - check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in check_peaks.__code__.co_varnames} + check_peaks_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(check_peaks).parameters.keys())} hvsr_results = check_peaks(hvsr_data=hvsr_results, verbose=verbose, **check_peaks_kwargs) - get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in get_report.__code__.co_varnames} + get_report_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in tuple(inspect.signature(get_report).parameters.keys())} get_report(hvsr_results=hvsr_results, verbose=verbose, **get_report_kwargs) if verbose: @@ -9595,8 +10080,9 @@

    Raises

    #We do not need to plot another report if already plotted pass else: - hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} - hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, **hvplot_kwargs) + #hvplot_kwargs = {k: v for k, v in locals()['kwargs'].items() if k in plot_hvsr.__code__.co_varnames} + #hvsr_results['HV_Plot'] = plot_hvsr(hvsr_results, return_fig=True, show=False, close_figs=True) + pass else: pass @@ -9614,6 +10100,106 @@

    Raises

    return hvsr_results +
    +def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False) +
    +
    +

    Save settings to json file

    +

    Parameters

    +
    +
    settings_path : str, default="default"
    +
    Where to save the json file(s) containing the settings, by default 'default'. +If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. +If 'all' is selected, a directory should be supplied. +Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    +
    settings_type : str, {'all', 'instrument', 'processing'}
    +
    What kind of settings to save. +If 'all', saves all possible types in their respective json files. +If 'instrument', save the instrument settings to their respective file. +If 'processing', saves the processing settings to their respective file. By default 'all'
    +
    verbose : bool, default=True
    +
    Whether to print outputs and information to the terminal
    +
    +
    + +Expand source code + +
    def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False):
    +    """Save settings to json file
    +
    +    Parameters
    +    ----------
    +    settings_path : str, default="default"
    +        Where to save the json file(s) containing the settings, by default 'default'. 
    +        If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to.
    +        If 'all' is selected, a directory should be supplied. 
    +        Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    +    settings_type : str, {'all', 'instrument', 'processing'}
    +        What kind of settings to save. 
    +        If 'all', saves all possible types in their respective json files.
    +        If 'instrument', save the instrument settings to their respective file.
    +        If 'processing', saves the processing settings to their respective file. By default 'all'
    +    verbose : bool, default=True
    +        Whether to print outputs and information to the terminal
    +
    +    """
    +    fnameDict = {}
    +    fnameDict['instrument'] = "instrument_settings.json"
    +    fnameDict['processing'] = "processing_settings.json"
    +
    +    if settings_path == 'default' or settings_path is True:
    +        settingsPath = resource_dir.joinpath('settings')
    +    else:
    +        settings_path = pathlib.Path(settings_path)
    +        if not settings_path.exists():
    +            if not settings_path.parent.exists():
    +                print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}')
    +                settingsPath = pathlib.Path.home()
    +            else:
    +                settingsPath = settings_path.parent
    +        
    +        if settings_path.is_dir():
    +            settingsPath = settings_path
    +        elif settings_path.is_file():
    +            settingsPath = settings_path.parent
    +            fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json"
    +            fnameDict['processing'] = settings_path.name+"_processingSettings.json"
    +
    +    #Get final filepaths        
    +    instSetFPath = settingsPath.joinpath(fnameDict['instrument'])
    +    procSetFPath = settingsPath.joinpath(fnameDict['processing'])
    +
    +    #Get settings values
    +    instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"]
    +    procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report]
    +
    +    instrument_settings_dict = {}
    +    processing_settings_dict = {}
    +
    +    for k in instKeys:
    +        if isinstance(hvsr_data[k], pathlib.PurePath):
    +            instrument_settings_dict[k] = hvsr_data[k].as_posix()
    +        else:
    +            instrument_settings_dict[k] = hvsr_data[k]
    +    
    +    for func in procFuncs:
    +        funcName = func.__name__
    +        processing_settings_dict[funcName] = {}
    +        for arg in inspect.getfullargspec(func)[0]:
    +            if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)):
    +                pass
    +            else:
    +                processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg]
    +    
    +    #Save settings files
    +    if settings_type.lower()=='instrument' or settings_type.lower()=='all':
    +        with open(instSetFPath.as_posix(), 'w') as instSetF:
    +            json.dump(instrument_settings_dict, instSetF)
    +    if settings_type.lower()=='processing' or settings_type.lower()=='all':
    +        with open(procSetFPath.as_posix(), 'w') as procSetF:
    +            json.dump(processing_settings_dict, procSetF)        
    +
    +
    def test_function()
    @@ -10050,7 +10636,9 @@

    Returns

    Parameters ---------- export_path : filepath, default=True - Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True + Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). + By default True. + If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True ext : str, optional The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension. """ @@ -10372,7 +10960,9 @@

    Parameters

    Parameters

    export_path : filepath, default=True
    -
    Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True
    +
    Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). +By default True. +If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True
    ext : str, optional
    The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension.
    @@ -10386,7 +10976,9 @@

    Parameters

    Parameters ---------- export_path : filepath, default=True - Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). By default True. If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True + Filepath to save file. Can be either directory (which will assign a filename based on the HVSRData attributes). + By default True. + If True, it will first try to save each file to the same directory as datapath, then if that does not work, to the current working directory, then to the user's home directory, by default True ext : str, optional The extension to use for the output, by default 'hvsr'. This is still a pickle file that can be read with pickle.load(), but will have .hvsr extension. """ @@ -10551,6 +11143,7 @@

    Index

  • remove_noise
  • remove_outlier_curves
  • run
  • +
  • save_settings
  • test_function
  • diff --git a/docs/sprit_utils.html b/docs/sprit_utils.html index d3baac2..5de3f03 100644 --- a/docs/sprit_utils.html +++ b/docs/sprit_utils.html @@ -47,7 +47,7 @@

    Module sprit.sprit_utils

    channel_order = {'Z': 0, '1': 1, 'N': 1, '2': 2, 'E': 2} def check_gui_requirements(): - print("Checking requirements for gui") + #First, check requirements # Define a command that tries to open a window command = "python -c \"import tkinter; tkinter.Tk()\"" @@ -60,7 +60,7 @@

    Module sprit.sprit_utils

    oktoproceed=True else: oktoproceed=False - print("GUI window could not be created.") + print("GUI window cannot be created.") return oktoproceed @@ -339,6 +339,16 @@

    Module sprit.sprit_utils

    #Make input data (dict) into sprit_hvsr class def make_it_classy(input_data, verbose=False): if isinstance(input_data, (sprit_hvsr.HVSRData, sprit_hvsr.HVSRBatch)): + for k, v in input_data.items(): + if k=='input_params': + for kin in input_data['input_params'].keys(): + if kin not in input_data.keys(): + input_data[kin] = input_data['input_params'][kin] + if k=='params': + for kin in input_data['params'].keys(): + print(kin) + if kin not in input_data.keys(): + input_data[kin] = input_data['params'][kin] output_class = input_data else: output_class = sprit_hvsr.HVSRData(input_data) @@ -464,7 +474,7 @@

    Functions

    Expand source code
    def check_gui_requirements():
    -    print("Checking requirements for gui")
    +    #First, check requirements
         # Define a command that tries to open a window
         command = "python -c \"import tkinter; tkinter.Tk()\""
     
    @@ -477,7 +487,7 @@ 

    Functions

    oktoproceed=True else: oktoproceed=False - print("GUI window could not be created.") + print("GUI window cannot be created.") return oktoproceed @@ -855,6 +865,16 @@

    Returns

    def make_it_classy(input_data, verbose=False):
         if isinstance(input_data, (sprit_hvsr.HVSRData, sprit_hvsr.HVSRBatch)):
    +        for k, v in input_data.items():
    +            if k=='input_params':
    +                for kin in input_data['input_params'].keys():
    +                    if kin not in input_data.keys():
    +                        input_data[kin] = input_data['input_params'][kin]
    +            if k=='params':
    +                for kin in input_data['params'].keys():
    +                    print(kin)
    +                    if kin not in input_data.keys():
    +                        input_data[kin] = input_data['params'][kin]                
             output_class = input_data
         else:
             output_class = sprit_hvsr.HVSRData(input_data)
    diff --git a/pyproject.toml b/pyproject.toml
    index 7d62bff..8fb79f3 100644
    --- a/pyproject.toml
    +++ b/pyproject.toml
    @@ -7,7 +7,7 @@ name = "sprit"
     authors = [{name="Riley Balikian"}, {name="Hongyu Xaio"}]
     dynamic = ["readme"]
     license = {file = "LICENSE"}
    -version="0.1.50"
    +version="0.1.51"
     description = "A package for processing and analyzing HVSR (Horizontal to Vertical Spectral Ratio) data"
     keywords = ["HVSR", "seismic", "horizontal to vertical spectral ratio", "obspy", 'geology', 'geophysics', 'geotechnical']
     requires-python = ">=3.9"
    diff --git a/setup.py b/setup.py
    index 3b9f9de..a2fe0ed 100644
    --- a/setup.py
    +++ b/setup.py
    @@ -7,7 +7,7 @@
         name="sprit",
         author= "Riley Balikian",
         author_email = "balikian@illinois.edu",
    -    version="0.1.50",
    +    version="0.1.51",
         package_data={'sprit': ['resources/*', 'resources/icon/*', 'resources/themes/*', 'resources/themes/forest-dark/*', 
                                 'resources/themes/forest-light/*', 'resources/sample_data/*','resources/settings/*']},
         long_description_content_type="text/markdown",
    diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc
    index a750cdb6432e5eb5878fc1600ad5fe0ad04a68ef..842fd6b7603953ae80a5a5ba1dd629670e928412 100644
    GIT binary patch
    delta 11631
    zcmcgSd3=;bvQs@rCYi~7-;;180|Wv{Fc8iFa>-%BH5_3`&m#pyqi;Ju4s&9E!Js|;i-|>5YJmOSWS5;S4
    zSD#;(+dqrD@AJ3>UwnL=4*y+rjB^>DJ(TbegHJnu&H9D@z$fxa8^JM=|B+AS(-51)
    zKjYK+48$h$f8(?GY{aVhzw>!~K4Me&=e&m3A~uzO!58vHh)v^*`4aBnOB-~2*)YxQox^m3&O7fl+~w@d|eack>2RID>!5
    zJ-i9Anfy<@g?ka3#eKY$ui;iMxSt2G1>m2BS718}Z{urGW;Q?1*YWj;UCY1X*K-ZA
    zIsDK31|C9eF29lAv{C1n$8Y8vu$<2~^3WzkuH&2eEm+p@TlvqhtmU`y+p%20@8Dao
    zT*!aU@5FKuzrgR}zd%)s`Pckz-ig=}zLnpDJO}>^-^L|UmhyY~cBCxh-|!uLCsLLp
    zwu|pZYz4oM??GN2Qo4BeMhH1p^1XZ?lAQcoem{Qzg;w#u@(1}th}C0MFfQsB=ia&}0MIeR$lJCbBoIlJTL2WMnD1Qt~H$T7+V%fkS=ZCOt|2TP$wI&+rIR+WC9@eN@%Kf8ZbR50P>`
    zKg-V{MdKgwkFgB$Px$Y#yn(YCo&87WSkUR>Y8yR%TZ`}T?~H%{qO{&hYoq618J4tG
    zX5r7DX3*&Zx(&K6=+*_}J1<^H4<%^vs+Oqfw1jM3FiA_&5;;`qqIJ%)bzGmV6Jt7J
    z14drp(P@eI!_NV9i*#!eKu3Q8la{bk#|^vL2Ba@R`kDl8tkG!}u~5@w
    zC77;YhJu+IbXj>^Hw3c+@mkgfKqGO>STH-7qvdoZbnCP%ExSs$=K4TlAW2Kx0A0yi
    zRyS+_5!#0hkbn&mRfAkDSIb(f6MMC+t`sd7Ri=Z^3u`)i6=y2BJZ{nQQ0_S`j~lsl
    z6UIqPQFUsUhwU;{yN8s?)XPl1kD9O^wHQJl^R;|s@;zlTADd>XrYE#?Ekny}&<6{&
    z0yRj57=d_wL)y)Un}ZL(`;J*Y#qf>E5=$u
    zZ>^B9Ahyy9wNQT?X{Esb7i^SHgAhoh5NgHTTBSo@ilBSk;q#;2V3WhvqfmjLGTNDapaw0G_T)?+@f^
    zIyY!~ZLn6#Gc+?!!OUGI%^ID)XsZZivy|*BD1no|cObEbUH>22hm?YBrQpvVU2uq2
    zihZ&K^Kd=n(MsSs+7K~gGPqeyEc%N(K%sMg=O^EWhuVC?*6i^IhS<2v?QClf*v8pP
    zrZ23y&gS%bZLPNI!97ULys6bCosa!>QCwM6pu|?zY5i_^XsB&&Yry04`fX0(vU$Aq
    z&24zF*;culee1@=*)IEs4O>fW{zl(Al(nsOiGZix*=*y!KmZ#wd%P~2GhlNI*P1q$
    zx4zxxbK3%qE?e;`mp@Q!YjrtS|5P3N7%CwLeF0~{bt}B%&^iS2|m{iZbb{-CpunW9ZTZ$4KHt{C}T~p)053|I=*5&Z-T;Ru;1PTV3@5;cF1i7MnlN-rN)P
    zo<-L?7i~UV@oExYF{iKG6dHZP)8X?5{&8ENY2LTA`&RU||IOa;zZi_4Y%ra!A8tBa
    z{vR6*@RWQKKp3B5I$fWvOp9nub5`0i}3KfVq2|R>kd!cxexH||uOS)c=6Gy>o
    z@UXO2;L$uee4qlp%7l02o;mOoRLdoE;ZvxQ6XwBiSS;7fgDh~!+vdSdFg0wQ56=TE
    zlP9l(W*8Q(s)6^YnzaBd|D%QzI
    zG#CUS`HqGYBt|X^LLQ6_w+CUH2~6Rnjj$TPBDGEMFr>v!-{ECBNWw2E+HXr34d|nFI}Bk@
    z=sOS9=gLR6Lyq)qhboZr(e1D}<~%7mte9|bxMT-B4scMO-U%T%Bp2_(_{PZXyWkXj
    z5N_HHFG2jT;&J|CBb^n*)6)9@mJ{(q6b7;CJC&`(Pg!ccR0e
    z%P9|l9d?rZm&O;d`S)_=gOCX?hSxucAA}}13KPh&$$#RS$HJ|$T
    zyqy0Gq`_~c`x#h}eJ9bYDQ*f(AuIvKPRX~Qfe|JZkt1R2voHXLzDBYq2)s&Q1!;Sc
    zu$KtDOyGHHzn&PoW!({+GsmTN1geYQB<33gP7-(wB^;*Kj@fv96N6~LPRWmsKqY#e
    z`y5Ev6F&SL+{&O*Rvkknrtp$u&>9E7mao2sj=Uk<$MPbm(jF)7Cp`;DMUa%eO4w_1?x(P7$bK@K
    zfMIbtTk3jlQsO*yaV$297`?7_H33mcgooq@pW-fln>73ZY9KDW_z&=o5m)7~FQ9+=
    z=Ol5C6hA>Go+j`!+4u#FDT>37VtZC}1MJ;egro+~Q3H#7;R|SjvvTN{Pz3wsyf2|J
    z`ypailNJTkMA?h9r{%3*Vn6qW-}w^mGk{gLU4Vrp%0x!ggxbfpp=cylxLWWio#EwT
    z7uJPAUbq1BE%E4&NFs1n&iNV-o&D1DH53^4q0D#kj;~=Z>M+oS$qo@ii;uf%MTVU40}hB)dVhd^mMjv>B#gA)q{>fpR3^3EtAy
    zT8D9IZFc!ZqWtLZV4oNbGP##bc2fwg~igT*mL67h(&sE^qpiQMv1
    ziED8N=-SdOy^IZmm*jnnWm-;9jG`yLJj&P@cw7F!SoTyk(BzH7%6)aM^?};xIoj)r
    z!-90L&*OJ3@_5zZ>xe~4G@3oftz4pKo$##uQqSmMuQ9N4econNDlF7@9Ic+4Ng8^w
    zUoQ&fo*3qU4f6XKw#ZaQu{Fy@W;VX^HFEPa8s6U9i9JV*LWxLBeY2;2^&FoVLp5`&
    zGtj6u%d4q*rF_H81}Bis2x~9L-*EA-GA5Q)fmu$8WhHn{HODeP%n6^1Ws6}{6}3{<
    zc9Wld*5Z{lWkgINp{Rbsrpguz8)#Bn&t|#Z!Un)1`5Ox=pDX`pVc$WTJZEJ^P$pSC
    z8$Ga3XF^nG1t}SQrOq+3E}mt=dATm0l^7=@vq|oYM={SbAdnzSh}1+hq6U7gQcr-Ivo7gs;M6j}`#
    zV8w}S&|ehG!+%X+L56d?Et&1GFjA){P$qLy*woo-Z#YgvMn_BFTsw>&m{vRa)l5VdcT&mdE3-%}$wld9Jc
    zm`-38f!PGEC194z@>y=b#e^>*;2=-$WccODQnnMi!mI7N5J=5`Sx4FO`T!LHcTMF-*S1Xjpxqi~LD^2jK5x3LXL*UH)owg}q7k5;g-!Bj_5
    zQ>AAtTLJ<3!dT{nb~&Jm&4TOXnktrMxq$@Kr{8(GwTdk<(w?(PUZ`SIL4>a!$104L
    zHuCWX0wLKx8MmmQ+&P(58E-@qeiTo}?eNAht7g}k=3P%xzb5}Jb#>7zP90xmH$?PU
    zh?|j1*m?)6YyIhzAuGR6!*MZTe^bgb09SFFynhA@;`eXyOy)3dM2QS}(@d7mHX1s|
    z9m)(JoQVq+n&t6XY(5l)b7r#(kmRH;p$mA)Cv2t6o5OOTUXGr_hFgXasfj?N^v_|%
    zdE{|K?^@rgVsB55Mt2KR#n0uTIT)}QnL3vZHLk}-L*=x&Y#gi)Z=B2Cj)5(5$s$%#
    zbR7!YO=2L8Vi2Vw272O%u*MFtg53Cp>{`SY+h-#SCr}|t7}e*yxC&c3;^%@!?Yx#c7$NVV8SfJ6oDV4gpa9)r>o?Pl?o%w5SUDxX00
    z{WEah(@tSFkANhc`Ehsx3*yupk$UN0e?&-1<@2`6+g7r|P=IO1E(~rYsT`y%MHwH~eHH_1F88-z@Jf@d733*uKF+IE(ZtmqR&{z{{
    z;pXfRrsa6t7R*^t6_r~wBbr)`H47$HtgD%D@%SJ%<%erQ!?gFvw0TJ*`g0(HdQIjMWrd2YfZYWo{IHk&DxX}NJrQ5c>V@GFW7=|R=CFD-`>w_1+MEn=IdVwCBp
    zVPr^458gjS6>jH)Fz{KJzD`zIQ$sLUV_p3*V>)=R$}Xb#fM`>M*ss7qN^^GWgL#;c
    z%-0eyd6%j(tvJ$6CFoFrmfuy1t?XJJx>3-rr#NFW?;z4*HOkW#;t&kdh7~pqb^{-x
    z
    zhtV#=#zQrd(D8B%?C@+{-+XkYj$hqJ;gvfLe9Ugf$MPyZj*rK`(R{)#1E07hCRmL9
    zAI2wX>6sWGG#-Z*M+bCG<(iF8zM5BWVU#@X8qTLgCs5Z2&D?{He|Qkh54
    z80H*x_G$7YXLYLuJ*TT}uXdW)OS|J%0!L{JyazFdv7MgIg(T{B0y_xYLHyrR(>kiI
    zAn+{iX3?{H7;#t9c3w`H`hLvU%iS)PH8_LFlZl*(m>nGERqcK}&G7Lgi&%?^r5>bm
    zT^g;A61OJ9uuvE-6#v(tRRaHR=Yl8Xm+^+5u@-lGzKF2fUi}~b+hzP&rz+;
    zs&4X=PTz)QcRv43>9X?^#BZ<@4x_&Xb8_Ds%7>0{cj#@?JH&G&xsQN4e(8#e$0J!1
    z4yaZiynBABtQSbQl_~&4a&IpTL>l6=CGn>5L%|>QcMBne#Ie3~n
    zaV>$Z@~E3-B>6~iHW79cXp~>MS>;GO#YMfX4J51tfg`>~#cZ~(-WkAqzz|{#CNPLV
    znOxSu@iZC~_ZRh?`tZ-r8*HV=jc=Lbv6l-^huD0lmyBFVbcT{
    zs2>3tjOZyW4PTqB_>^sB3Hls8sk37CBn}FYYr;#aiRD2#(o7c<^d@W@hdS_QG&4bt
    z34CMMiC5i=~cmf88k9k;SqNRz3YBhmoX>4Z`hO1>-LWEKRb_BKJInqNDm|hJj
    z8b^r`U6Hb_oy`t?tC$hP+7^$OpmV*r5e4nWh{5X-6HbR8Rje0M5H2VuVSqn6Q2X)W`iG
    zYNl3Hf1=p{zrX~2sEUUWIAZZB3YR{m-d-W1x`@)1)M56!T0G6ZhITQ48uqR{p(bSC
    zl2q$Wtvs~`%Sd=V`A=&@(99Dv$uIS>Qm)sTbwcf3Bjs?7rRLDGikL!e00RIuX8)=X-^qN(iH?o6Zjcvy@fm(Lsd20V+pGw
    z5M7Lfy+t}F5;jR53$h__Ec~Y+TWCtT1sQl%rVmGPJApgo!i{V)913sW$d1J1Sc#@y
    zifgF#W*Rw~zv2zqdIw9#m&VO^u#=Y4Bt{#8u*#KNaFLyn!7XeClLmQw3oC?o!=G(o
    z{mcVt-;aK=zk@A0tbJ+k@iy^uYJU{za>O?LfWArIu#L^YwCM3|_*EZ~7q+pR^7|ZB
    zCP!k|9i}GCREmH&Bkz*T21CM!B&&*nqw-Jpp(h_m%O0ejl|%Nhp_o5ex`!25&XB*G
    z37nG~_n`XC;R7gTv5yY=i3I5@P^4lN
    zKT*%h>mI@Pg442{iZin75qx!fJ$&L3HlCT+U^}}-td%JT@QePuy!rqe1humM0K0XW
    zMTAj9?VLH&=7=`5Hm!DAtsO)IqIM%v)ni&M9&D44QY)&-g+hf+L3A4KHN+4%lL@_8
    zh>nV52&UorctFlR$O=sA$h=v$B96JqI}fr^h3d&cWzbGi12QI#6Lyi*za~F9$SSW<
    zS+^5JRV#EQ^%mK9(;!S8j%xKW#UP;e5g<$*m`{+fU)Dd4oo<#tdz{tcV0`#E%TIck
    z%(%%aqi7~d%OQLfiOBhfSn9aR#Qc`penp_1BpxGLCpCDQuxW(#BgT^iDB&q6$0^*h
    z`w+e!#*q({I1>8^bQ7T5f*3&z^@cl_u*n3L6WBxCRfN$4n|j6)?S#?OkDz{w8&#D6
    zJ(r02KjVpOn#~@Z)W3t7$o32{#k
    Al>h($
    
    delta 11339
    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
    
    diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py
    index 740c209..a24912f 100644
    --- a/sprit/sprit_hvsr.py
    +++ b/sprit/sprit_hvsr.py
    @@ -2040,32 +2040,32 @@ def report_output(_report_format, _plot_type='HVSR p ann C+ p ann Spec', _return
         return hvsr_results
     
     #Main function for plotting results
    -def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, xtype='freq', fig=None, ax=None, return_fig=False,  save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs):
    +def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False,  save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs):
         """Function to plot HVSR data
     
         Parameters
         ----------
         hvsr_data : dict                  
             Dictionary containing output from process_hvsr function
    -    plot_type : str='HVSR' or list    
    +    plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC'
             The plot_type of plot(s) to plot. If list, will plot all plots listed
    -        'HVSR' : Standard HVSR plot, including standard deviation
    -        - '[HVSR] p' : HVSR plot with BestPeaks shown
    -        - '[HVSR] p' : HVSR plot with best picked peak shown                
    -        - '[HVSR] p* all' : HVSR plot with all picked peaks shown                
    -        - '[HVSR] p* t' : HVSR plot with peaks from all time steps in background                
    -        - '[HVSR p* ann] : Annotates plot with peaks
    -        - '[HVSR] -s' : HVSR plots don't show standard deviation
    -        - '[HVSR] t' : HVSR plot with individual hv curves for each time step shown
    -        - '[HVSR] c' : HVSR plot with each components' spectra. Recommended to do this last (or just before 'specgram'), since doing c+ can make the component chart its own chart
    -        'Specgram' : Combined spectrogram of all components
    -        - '[spec]' : basic spectrogram plot of H/V curve
    +        - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below:
    +            - 'p' shows a vertical dotted line at frequency of the "best" peak
    +            - 'ann' annotates the frequency value of of the "best" peak
    +            - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified)
    +            - 't' shows the H/V curve for all time windows
    +                -'tp' shows all the peaks from the H/V curves of all the time windows
    +        - 'COMP' - plot of the PPSD curves for each individual component ("C" also works)
    +            - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR)
    +            - 'p' shows a vertical dotted line at frequency of the "best" peak
    +            - 'ann' annotates the frequency value of of the "best" peak
    +            - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified)
    +            - 't' shows the H/V curve for all time windows
    +        - 'SPEC' - spectrogram style plot of the H/V curve over time
    +            - 'p' shows a horizontal dotted line at the frequency of the "best" peak
    +            - 'ann' annotates the frequency value of the "best" peak
         use_subplots : bool, default = True
             Whether to output the plots as subplots (True) or as separate plots (False)
    -    xtype : str, default = 'freq'    
    -        String for what to use, between frequency or period
    -            For frequency, the following are accepted (case does not matter): 'f', 'Hz', 'freq', 'frequency'
    -            For period, the following are accepted (case does not matter): 'p', 'T', 's', 'sec', 'second', 'per', 'period'
         fig : matplotlib.Figure, default = None
             If not None, matplotlib figure on which plot is plotted
         ax : matplotlib.Axis, default = None
    @@ -2189,6 +2189,11 @@ def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True
                     plotComponents[0] = plotComponents[0][:-1]
                     _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs)
                 elif p=='spec':
    +                plottypeKwargs = {}
    +                for c in plotComponents:
    +                    print(c)
    +                    plottypeKwargs[c] = True
    +                kwargs.update(plottypeKwargs)
                     _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs)
                 else:
                     warnings.warn('Plot type {p} not recognized', UserWarning)   
    @@ -5681,18 +5686,28 @@ def __plot_current_fig(save_dir, filename, fig, ax, plot_suffix, user_suffix, sh
     def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix='',**kwargs):
         """Private function for plotting average spectrogram of all three channels from ppsds
         """
    +    # Get all input parameters
         if fig is None and ax is None:
             fig, ax = plt.subplots()    
     
    +    print(kwargs.keys())
         if 'kwargs' in kwargs.keys():
             kwargs = kwargs['kwargs']
     
    -    if 'peak_plot' in kwargs.keys():
    +    if 'spec' in kwargs.keys():
    +        del kwargs['spec']
    +
    +    if 'p' in kwargs.keys():
             peak_plot=True
    -        del kwargs['peak_plot']
    +        del kwargs['p']
         else:
             peak_plot=False
    -        
    +
    +    if 'ann' in kwargs.keys():
    +        annotate=True
    +        del kwargs['ann']
    +    else:
    +        annotate=False
     
         if 'grid' in kwargs.keys():
             ax.grid(which=kwargs['grid'], alpha=0.25)
    @@ -5725,33 +5740,22 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix
         else:
             kwargs['cmap'] = 'turbo'
     
    +    # Setup
         ppsds = hvsr_data['ppsds']#[k]['current_times_used']
         import matplotlib.dates as mdates
         anyKey = list(ppsds.keys())[0]
    -
    -    psdHList = []
    -    psdZList = []
    -    for k in hvsr_data['psd_raw']:
    -        if 'z' in k.lower():
    -            psdZList.append(hvsr_data['psd_raw'][k])    
    -        else:
    -            psdHList.append(hvsr_data['psd_raw'][k])
         
    -    #if detrend:
    -    #    psdArr = np.subtract(psdArr, np.median(psdArr, axis=0))
    +    # Get data
         psdArr = np.stack(hvsr_data['hvsr_df']['HV_Curves'].apply(np.flip))
         useArr = np.array(hvsr_data['hvsr_df']['Use'])
         useArr = np.tile(useArr, (psdArr.shape[1], 1)).astype(int)
    -    useArr = np.clip(useArr, a_min=0.3, a_max=1)
    -    #psdArr = hvsr_data['ind_hvsr_curves'].T
    -    #print(psdArr.shape)
    +    useArr = np.clip(useArr, a_min=0.15, a_max=1)
     
    +    # Get times
         xmin = hvsr_data['hvsr_df']['TimesProcessed_MPL'].min()
         xmax = hvsr_data['hvsr_df']['TimesProcessed_MPL'].max()
     
    -    #xmin = min(hvsr_data['ppsds'][anyKey]['current_times_used'][:-1]).matplotlib_date
    -    #xmax = max(hvsr_data['ppsds'][anyKey]['current_times_used'][:-1]).matplotlib_date
    -  
    +    #Format times
         tTicks = mdates.MinuteLocator(byminute=range(0,60,5))
         ax.xaxis.set_major_locator(tTicks)
         tTicks_minor = mdates.SecondLocator(bysecond=[0])
    @@ -5761,15 +5765,16 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix
         ax.xaxis.set_major_formatter(tLabels)
         ax.tick_params(axis='x', labelsize=8)
     
    +    #Get day label for bottom of chart
         if hvsr_data['hvsr_df'].index[0].date() != hvsr_data['hvsr_df'].index[-1].date():
             day = str(hvsr_data['hvsr_df'].index[0].date())+' - '+str(hvsr_data['hvsr_df'].index[-1].date())
         else:
             day = str(hvsr_data['hvsr_df'].index[0].date())
     
    +    #Get extents
         ymin = hvsr_data['input_params']['hvsr_band'][0]
         ymax = hvsr_data['input_params']['hvsr_band'][1]
     
    -
         freqticks = np.flip(hvsr_data['x_freqs'][anyKey])
         yminind = np.argmin(np.abs(ymin-freqticks))
         ymaxind = np.argmin(np.abs(ymax-freqticks))
    @@ -5778,40 +5783,29 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix
     
         extList = [xmin, xmax, ymin, ymax]
     
    -    #Set up axes, since data is already in semilog
    -    #axy = ax.twinx()
    -    #axy.set_yticks([])
    -    #axy.zorder=1
    -    #ax.zorder=0
    -    ax.set_facecolor([0,0,0]) #Create transparent background for front axis
    -    #ax.set_facecolor('#ffffff00') #Create transparent background for front axis
    -    #plt.sca(axy)  
    -    #psdArr = np.flip(psdArr, axis=0)
    +    #Set up axes
    +    ax.set_facecolor([0,0,0]) #Create black background for transparency to look darker
     
    +    # Interpolate into linear
         new_indices = np.linspace(freqticks[0], freqticks[-1], len(freqticks))
    -    # use nupy.interp to interpolate your original array onto the new indices
         linList = []
         for row in psdArr:
             row = row.astype(np.float16)
             linList.append(np.interp(new_indices, freqticks, row))
         linear_arr = np.stack(linList)
     
    -    #im = ax.imshow(psdArr.T, origin='lower', extent=extList, aspect='auto', interpolation=None, alpha=useArr, **kwargs)
    -    im = ax.imshow(linear_arr.T, origin='lower', extent=extList, aspect='auto',alpha=useArr, **kwargs)
    -    #x=hvsr_data['hvsr_df']['TimesProcessed_MPL']
    -    #y=freqticks
    -    #z=psdArr.T
    -    #im = ax.pcolormesh(x, y, z, alpha=useArr, **kwargs)
    -    ax.tick_params(left=True, right=True)
    -    #plt.sca(ax)
    -    peak_plot=True
    +    # Create chart 
    +    im = ax.imshow(linear_arr.T, origin='lower', extent=extList, aspect='auto', alpha=useArr, **kwargs)
    +    ax.tick_params(left=True, right=True, top=True)
    +
         if peak_plot:
             ax.axhline(hvsr_data['BestPeak']['f0'], c='k',  linestyle='dotted', zorder=1000)
     
    -    #FreqTicks =np.arange(1,np.round(max(hvsr_data['x_freqs'][anyKey]),0), 10)
    -    #specTitle = ax.set_title(hvsr_data['input_params']['site']+': Spectrogram')
    -    #bgClr = (fig.get_facecolor()[0], fig.get_facecolor()[1], fig.get_facecolor()[2], 0.1)
    -    #specTitle.set_color(bgClr)
    +    if annotate:
    +        xLocation = float(xmin) + (float(xmax)-float(xmin))*0.98
    +        ann = ax.text(x=xLocation, y=float(hvsr_data['BestPeak']['f0'])+0.3, fontsize='small', s=f"{hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va='bottom', 
    +                      bbox={'alpha':0.8, 'edgecolor':'w', 'fc':'w', 'pad':0.3})
    +
         ax.set_xlabel('UTC Time \n'+day)
         if colorbar:
             cbar = plt.colorbar(mappable=im, orientation='horizontal')
    @@ -5819,16 +5813,12 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix
     
         ax.set_ylabel(ylabel)
         ax.set_yscale('log')
    -    #ax.semilogy()
    -
    -    #ax.set_ylim(hvsr_data['input_params']['hvsr_band'])
     
    -    #fig.tight_layout()
    +    #plt.sca(ax)
         #plt.rcParams['figure.dpi'] = 500
         #plt.rcParams['figure.figsize'] = (12,4)
         fig.canvas.draw()
    -    #fig.tight_layout()
    -    #plt.show()
    +
         return fig, ax
     
     #Plot spectrogram from stream
    
    From 890beb2a21444863d5ae2312b1df72b43065e5ad Mon Sep 17 00:00:00 2001
    From: RJbalikian <46536937+RJbalikian@users.noreply.github.com>
    Date: Sat, 28 Oct 2023 14:12:45 -0500
    Subject: [PATCH 10/17] update print report format
    
    ---
     docs/sprit_hvsr.html                         |   6 +++---
     sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 177566 -> 177522 bytes
     sprit/sprit_hvsr.py                          |   6 +++---
     3 files changed, 6 insertions(+), 6 deletions(-)
    
    diff --git a/docs/sprit_hvsr.html b/docs/sprit_hvsr.html
    index c420748..cff6f14 100644
    --- a/docs/sprit_hvsr.html
    +++ b/docs/sprit_hvsr.html
    @@ -6206,9 +6206,9 @@ 

    Module sprit.sprit_hvsr

    _peak[_i]['Report']['Nc'] = f'{int(nc)} > 200 {sprit_utils.x_mark()}' if test3: - _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.check_mark()}' + _peak[_i]['Report']['σ_A(f)'] = f'H/V Amp. St.Dev. for {peakFreq*0.5:0.3f}-{peakFreq*2:0.3f}Hz < {compVal} {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.x_mark()}' + _peak[_i]['Report']['σ_A(f)'] = f'H/V Amp. St.Dev. for {peakFreq*0.5:0.3f}-{peakFreq*2:0.3f}Hz < {compVal} {sprit_utils.x_mark()}' _peak[_i]['PassList']['WindowLengthFreq.'] = test1 _peak[_i]['PassList']['SignificantCycles'] = test2 @@ -6262,7 +6262,7 @@

    Module sprit.sprit_hvsr

    if (float(_peak[_i]['f0']) / 4.0 <= _x[_j] < float(_peak[_i]['f0'])) and float(_peak[_i]['A0']) / _y[_j] > 2.0: _peak[_i]['Score'] += 1 _peak[_i]['f-'] = '%10.3f %1s' % (_x[_j], sprit_utils.check_mark()) - _peak[_i]['Report']['A(f-)'] = f"Amp. of H/V Curve @{_x[_j]:0.3f}Hz ({_y[_j]:0.3f}) < {_peak[_i]['A0']/2:0.3f} (peak amp. {_peak[_i]['A0']:0.3f}) {sprit_utils.check_mark()}" + _peak[_i]['Report']['A(f-)'] = f"Amp. of H/V Curve @{_x[_j]:0.3f}Hz ({_y[_j]:0.3f}) < {_peak[_i]['A0']/2:0.3f} {sprit_utils.check_mark()}" _peak[_i]['PassList']['PeakProminenceBelow'] = True break else: diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 842fd6b7603953ae80a5a5ba1dd629670e928412..b59230addf0401ad3221c13dbac1aa486c864d75 100644 GIT binary patch delta 800 zcma)4&ubG=5S}-ipW9|NrKQxQ$!pUe7BsC?YKbc-)e^mkMC(Ng+LvC$)~suh@F0}Z zfCp`UC{Kx%^j5HkBHBH8>CJz@sFWHnUbGkS=*hQnTXOK=!m#rVGv9tQ-@e>afBjIk z`9MGg`MkPuCS7~Cs4Z*ct%ld~L3Nl5|HowOzb5%UQ%@HhW4$_X)uEOJ7Lz#8g!^>J#5qa9 zP`}mj5YM^?vK`6cDSGbC-8fB|@$>10IGvq0=x^6Ze00{x5rC{anYGNvc#$XxGA_1P zFsio5V@$}n*Dk)V;3e`#oGjoXF~n8@N4>QSD8q{Ujnj7V%0jD!UIibyG23!c(COrq3z1cfk_5 z?J5vswV`0jR&b<&#d-;&x$uDs#Tki6&K?d8FMQIoUgubL9&>uUQkg3R%&zqvFzkQS z-osYbyoYpD?xFahKr(}wZ(Na*)=dzTWgPC_WBpCUHtB$=a_R-rl-xo21>lh*We9P^T0@wAGR)Wud6uamEqwuYBmJYQ_G9Wk?k4@lkG O-N3wCzGq(JY2^>-iqePx delta 804 zcmb7COK1~O6n*bbKFu_VjZM&6)4Zms_JgTfFc2fucGC(XQmvp1iA(~Lnl{r^G!7IA z4Jc?c$>J(OiHq(c6hTJtgRWe<6vVW}g$s41E4$GDi<{m>FMsNVvnAd@r39CF#CThRDqrYSI`b`>Q}Ozlva<*^rtICs zt}PtYeDt;}FV|Ziu^M~OqH)d9!IWK8p#;l{|4?xMtB^1yw7-&qB)bXAmW7GKggx7E zPlDy58lPnS5yO59M(m;acAE2D@#1ljo>4_|dRnB_RBleF3!JeH$;jM=2`-KD5j2({ zBzuTGMl~0~^(8og4ISDrUIvvH@NpT2oy{z8mc4+v3hV*3iFy?RxLb#b0Sz=eeUg*P zU9%~{yrb^goJh7hUh3>fP+E9Hhfe+(QHNe0!k0Rn_6cgVSwtTZyD?OOSl}e({YTf| z!HPU%D|2-jGZpCJ%ci$KuCdL)P0R3>rDScQ{FEMx(L#)fm6f^f#Wy;%^L?1qA<%33 zcWw7iFq%_xu`#3eYdksPAozsds>G zaXK}1kyZuI_|TLXn^&) 200 {sprit_utils.x_mark()}' if test3: - _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.check_mark()}' + _peak[_i]['Report']['σ_A(f)'] = f'H/V Amp. St.Dev. for {peakFreq*0.5:0.3f}-{peakFreq*2:0.3f}Hz < {compVal} {sprit_utils.check_mark()}' else: - _peak[_i]['Report']['σ_A(f)'] = f'σ_A for all freqs {round(peakFreq*0.5, 3)}-{round(peakFreq*2, 3)} < {compVal} {sprit_utils.x_mark()}' + _peak[_i]['Report']['σ_A(f)'] = f'H/V Amp. St.Dev. for {peakFreq*0.5:0.3f}-{peakFreq*2:0.3f}Hz < {compVal} {sprit_utils.x_mark()}' _peak[_i]['PassList']['WindowLengthFreq.'] = test1 _peak[_i]['PassList']['SignificantCycles'] = test2 @@ -6231,7 +6231,7 @@ def __check_clarity(_x, _y, _peak, do_rank=True): if (float(_peak[_i]['f0']) / 4.0 <= _x[_j] < float(_peak[_i]['f0'])) and float(_peak[_i]['A0']) / _y[_j] > 2.0: _peak[_i]['Score'] += 1 _peak[_i]['f-'] = '%10.3f %1s' % (_x[_j], sprit_utils.check_mark()) - _peak[_i]['Report']['A(f-)'] = f"Amp. of H/V Curve @{_x[_j]:0.3f}Hz ({_y[_j]:0.3f}) < {_peak[_i]['A0']/2:0.3f} (peak amp. {_peak[_i]['A0']:0.3f}) {sprit_utils.check_mark()}" + _peak[_i]['Report']['A(f-)'] = f"Amp. of H/V Curve @{_x[_j]:0.3f}Hz ({_y[_j]:0.3f}) < {_peak[_i]['A0']/2:0.3f} {sprit_utils.check_mark()}" _peak[_i]['PassList']['PeakProminenceBelow'] = True break else: From a7e9cc8bed6db206405b65941ea421e9d7719dc6 Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Sat, 28 Oct 2023 14:14:14 -0500 Subject: [PATCH 11/17] minor updates to stop print --- sprit/sprit_hvsr.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index e473d76..ec1eea0 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -2191,7 +2191,6 @@ def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True elif p=='spec': plottypeKwargs = {} for c in plotComponents: - print(c) plottypeKwargs[c] = True kwargs.update(plottypeKwargs) _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) @@ -5690,7 +5689,6 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix if fig is None and ax is None: fig, ax = plt.subplots() - print(kwargs.keys()) if 'kwargs' in kwargs.keys(): kwargs = kwargs['kwargs'] From b8537ff89fc08431402b62c8457aba62a6be3930 Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Sun, 29 Oct 2023 10:12:53 -0500 Subject: [PATCH 12/17] update export settings function --- sprit/__init__.py | 8 +- sprit/__pycache__/__init__.cpython-310.pyc | Bin 1329 -> 1374 bytes sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 177522 -> 177752 bytes sprit/sprit_hvsr.py | 166 ++++++++++--------- 4 files changed, 93 insertions(+), 81 deletions(-) diff --git a/sprit/__init__.py b/sprit/__init__.py index 3ebff50..6b7c8e0 100644 --- a/sprit/__init__.py +++ b/sprit/__init__.py @@ -10,7 +10,9 @@ from sprit.sprit_hvsr import( run, export_data, + export_settings, import_data, + import_settings, input_params, gui, get_metadata, @@ -20,7 +22,6 @@ process_hvsr, plot_hvsr, remove_noise, - save_settings, check_peaks, get_report, HVSRData, @@ -52,8 +53,10 @@ 'get_char', 'time_it', 'checkifpath', - 'export_data', + 'export_data', + 'export_settings', 'import_data', + 'import_settings', 'input_params', 'gui', 'get_metadata', @@ -64,7 +67,6 @@ 'process_hvsr', 'plot_hvsr', 'remove_noise', - 'save_settings', 'check_peaks', 'get_report', 'HVSRData', diff --git a/sprit/__pycache__/__init__.cpython-310.pyc b/sprit/__pycache__/__init__.cpython-310.pyc index 672c9e7f6e6a52a4292503e648cbe8fb3bb9e019..4268e36b8817de6b28959df01371602ac0ad6c02 100644 GIT binary patch delta 390 zcmdnUb&rcTpO=@50SK}e*`~go$SccuWTJK{Pp(9i1S3NVV+ul9J54^x|9G znYl=^P!3FXaw}umWG<%1;cBhfWr`fgyu_TG`1o5~Kt^dvMt)Iz{7Qx*Rgk+> UC;woH;qqZJ5#ixuVdYQ<0Dlc?)Bpeg diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index b59230addf0401ad3221c13dbac1aa486c864d75..eda357434b3c5f9624d25eb21aa49745b8944b0e 100644 GIT binary patch delta 19345 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 delta 19301 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 Date: Sun, 29 Oct 2023 14:56:02 -0500 Subject: [PATCH 13/17] import export settings updated --- sprit/__pycache__/__init__.cpython-310.pyc | Bin 1374 -> 1362 bytes sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 177752 -> 177698 bytes sprit/sprit_hvsr.py | 143 +++++++++++-------- 3 files changed, 81 insertions(+), 62 deletions(-) diff --git a/sprit/__pycache__/__init__.cpython-310.pyc b/sprit/__pycache__/__init__.cpython-310.pyc index 4268e36b8817de6b28959df01371602ac0ad6c02..99f85f0f2f1390e2ca718b34284ee93c19c6da77 100644 GIT binary patch delta 29 jcmcb|b%~2RpO=@50SHRV>^5@iF;9+Q5#1cc%)$f!YS9Lh delta 43 xcmcb_b&rcXpO=@50SK}e*>2?4V-}3!&&(~zFDi*IPE9Sz%u6rcY{x9Z1ONen40-?n diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index eda357434b3c5f9624d25eb21aa49745b8944b0e..0576e4ea207101cc24bf98dc59a8a761f5c0c249 100644 GIT binary patch delta 22145 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 F{{`Z8(%%38 delta 21873 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%0A*;^EYa#!IrlVv73R@VR?85@TP$kS-WEA@o)22 z{Bx>)ny~3gf?o8g{lQsWG@`O8#xuJ~A4AuJ0KDRDUez^5Q3Ch4t0 z%~qe^<$!+>byCdTprPsZMj8RJ@Uc1!rD6iUWJ+k#w;1QwSNOy9?%8NGi~<-5For;Z zedWz6#T#l41UnZ%11AJ)*mM~RB-PLuWD*6^-GCk0jb~aVvD~zXI-SHlV zD$s2(Z9dz+V^90|eY4G>3O`M#OM3^}xtlJ?NNYc^x1~Sy;$i+<7hlm-Ud$Ucm zOXpbAhJ#ynVBGYI7)3}vH@#0xpSqM=%$gcGKs;nFt&#JmJ_P;}z`0+SQ%WW(tLuF6 zn^;)*4jVMz=d4&mO(oD^1~XBLQqQTT`#O1~7m7L=l}|xt@M8E>(8JpQzD|zs)+-3v z)dMsDP&*|c(dh{@@&f4%ApKRkQ1g}>0*s*mg#^mB_ zg+iZHGfsPV&3;$TCrd;6fH`&nIiDPiN)iO^Ip+p!IfAs~u0WLw=Nf(2kf}dP!$*_x zZntizueHC@cNBb`xh?Ij*q^$ez8~?(5@DmrR%0;g-0Yhdnbnr8awnn2Yo52{Q2Ro2 z0t9nrq}%njqa6itSj_hQKgB~FIdkTeT_KLKIX71e#^1qs4wQKFv2)G$4qplKj`dlP zx@s~FEt_=_I(BYb&1&4WpLw(Lw&e|sZL2pyT-Z_e%gMZ9*%{=U zDvoj{q4ifcRoIx(I9*}C^X!XRTT!W7I`^R+uurn%ME0tGm7(S2ET?I^wrVkE^#bJg z5&%-S_E8x|!?$3f5v2_P2snMQ74>zLln&deXt()pi|n?Dm!89Z)hFd^ieEr(e>b3kLa0x0)9J{y5I2STs zPqim+mE(nQx1V*Pyw{nC`+Z6!?xrU5^Chy^vVBm!8vynL+(@955bUFi9)6IOh4eS1 zt65J4tJea18Z!-W74b%{0Ad!s6JVEVyj0E*e`tT;Qh9f5uT<3W9^vqm$-^M%7fs6! znIV2-uH7MrW-wJS*A(ZO;u%p~?D5dc2Y=lm`}Ht1Op|MNlV_5L-&NClWMuVwnZ~nI zrujMO;A5&QykpOey)>L!_NxSUfa^O6c++gd|2p`B-PYjHm(by@)dqr{WlCu1H%w%w zoats4Yp;28C*|DUZvSPc>=D<3Q{9ffUn2@lNwbHY>*2R-X##pLC_j$+c>Yhf=xq0} zTifKMv%OHiNlibX=&=QQ9pRVSVjT~r`ECosT1)FnKdIC7CDetNpebR{@Zut~YmdxN z+DOP{I*O+2%@cd1Zt0Q;OP$5S_xqT=ePFUMAe%E5jwLdZYJKh+`(=tZh-_4r*Z5+} z{l&jNkroo>sGj80`G`GdPMk=W^q+vq_9NHHa z{Wg`$H0vR27Aw^xBFje>z4P)8klD<2EiN15uKBPP!h8t*b+e#N_DKZS`bvOz&4xC* zTiDUw+9p3wIIC07Z6$953eJkAtR>+h3i*ogr5nU&o>B@Ui#++ffco#i!+Pa(X5&~NVu$9J&x*F=~5T)Ei z!Mzmvt}sjQl`C2V_)W_+$ zY~e!k7l2nK3{5b=<+Te zm(#bAJF+mBnMV)Fti%{B%(G~F!Mt-wj&FGyL-P5l&_qx4auiDJDf7|j#v@-t@G0-Y zBYV&H0oeips-nn;#C7T`@tf&C->cE|HDq!`02&F4-cxFz|5=ZXQZ#X`4g}Vfo9phA z1?itbA|C?aj)QJLi|J*WzuhNuGn!CY36KHm*%<6wlXh4RZn+G#pM&^U0I%b53rZUS z{sHt40NBrFkDLGhf_WhE9ZF%t4c8o{kbpvk_ncAInVmoww8q;{txF4nZ(FMF8>?w6+T*>nRz lzX{zLx)YZ3XD!v_#brvD2_BL8aZ)6FOf_M1!x4Gu{{z&3mQVly diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index b26ebc9..6da8dea 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -931,21 +931,23 @@ def _do_export(_hvsr_data=hvsr_data, _export_path=export_path, _ext=ext): ###WORKING ON THIS #Save default instrument and processing settings to json file(s) -def export_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False): +def export_settings(hvsr_data, export_settings_path='default', export_settings_type='all', include_location=False, verbose=False): """Save settings to json file Parameters ---------- - settings_path : str, default="default" + export_settings_path : str, default="default" Where to save the json file(s) containing the settings, by default 'default'. If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. If 'all' is selected, a directory should be supplied. Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory. - settings_type : str, {'all', 'instrument', 'processing'} + export_settings_type : str, {'all', 'instrument', 'processing'} What kind of settings to save. If 'all', saves all possible types in their respective json files. If 'instrument', save the instrument settings to their respective file. If 'processing', saves the processing settings to their respective file. By default 'all' + include_location : bool, default=False, input CRS + Whether to include the location parametersin the exported settings document.This includes xcoord, ycoord, elevation, elev_unit, and input_crs verbose : bool, default=True Whether to print outputs and information to the terminal @@ -954,23 +956,23 @@ def export_settings(hvsr_data, settings_path='default', settings_type='all', ver fnameDict['instrument'] = "instrument_settings.json" fnameDict['processing'] = "processing_settings.json" - if settings_path == 'default' or settings_path is True: + if export_settings_path == 'default' or export_settings_path is True: settingsPath = resource_dir.joinpath('settings') else: - settings_path = pathlib.Path(settings_path) - if not settings_path.exists(): - if not settings_path.parent.exists(): - print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}') + export_settings_path = pathlib.Path(export_settings_path) + if not export_settings_path.exists(): + if not export_settings_path.parent.exists(): + print(f'The provided value for export_settings_path ({export_settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}') settingsPath = pathlib.Path.home() else: - settingsPath = settings_path.parent + settingsPath = export_settings_path.parent - if settings_path.is_dir(): - settingsPath = settings_path - elif settings_path.is_file(): - settingsPath = settings_path.parent - fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json" - fnameDict['processing'] = settings_path.name+"_processingSettings.json" + if export_settings_path.is_dir(): + settingsPath = export_settings_path + elif export_settings_path.is_file(): + settingsPath = export_settings_path.parent + fnameDict['instrument'] = export_settings_path.name+"_instrumentSettings.json" + fnameDict['processing'] = export_settings_path.name+"_processingSettings.json" #Get final filepaths instSetFPath = settingsPath.joinpath(fnameDict['instrument']) @@ -978,6 +980,7 @@ def export_settings(hvsr_data, settings_path='default', settings_type='all', ver #Get settings values instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"] + inst_location_keys = ['xcoord', 'ycoord', 'elevation', 'elev_unit', 'input_crs'] procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report] instrument_settings_dict = {} @@ -989,6 +992,15 @@ def export_settings(hvsr_data, settings_path='default', settings_type='all', ver instrument_settings_dict[k] = hvsr_data[k].as_posix() else: instrument_settings_dict[k] = hvsr_data[k] + + if include_location: + for k in inst_location_keys: + if isinstance(hvsr_data[k], pathlib.PurePath): + #For those that are paths and cannot be serialized + instrument_settings_dict[k] = hvsr_data[k].as_posix() + else: + instrument_settings_dict[k] = hvsr_data[k] + for func in procFuncs: funcName = func.__name__ @@ -1000,16 +1012,27 @@ def export_settings(hvsr_data, settings_path='default', settings_type='all', ver processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg] #Save settings files - if settings_type.lower()=='instrument' or settings_type.lower()=='all': - with open(instSetFPath.as_posix(), 'w') as instSetF: + if export_settings_type.lower()=='instrument' or export_settings_type.lower()=='all': + with open(instSetFPath.with_suffix('.inst').as_posix(), 'w') as instSetF: jsonString = json.dumps(instrument_settings_dict, indent=2) + #Format output for readability jsonString = jsonString.replace('\n ', ' ') jsonString = jsonString.replace('[ ', '[') jsonString = jsonString.replace('\n ]', ']') + #Export instSetF.write(jsonString) - if settings_type.lower()=='processing' or settings_type.lower()=='all': - with open(procSetFPath.as_posix(), 'w') as procSetF: - json.dump(processing_settings_dict, procSetF) + if export_settings_type.lower()=='processing' or export_settings_type.lower()=='all': + with open(procSetFPath.with_suffix('.proc').as_posix(), 'w') as procSetF: + jsonString = json.dumps(processing_settings_dict, indent=2) + #Format output for readability + jsonString = jsonString.replace('\n ', ' ') + jsonString = jsonString.replace('[ ', '[') + jsonString = jsonString.replace('\n ]', ']') + jsonString = jsonString.replace('\n },','\n\t\t},') + jsonString = jsonString.replace('{ "', '\n\t\t{\n\t\t\t"') + jsonString = jsonString.replace(', "', ',\n\t\t\t"') + #Export + procSetF.write(jsonString) #Reads in traces to obspy stream def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detrend='spline', detrend_order=2, update_metadata=True, plot_input_stream=False, verbose=False, **kwargs): @@ -1057,6 +1080,7 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr print('\nFetching data (fetch_data())') print() + print(params.items()) params = get_metadata(params, update_metadata=update_metadata, source=source) inv = params['inv'] date=params['acq_date'] @@ -2310,8 +2334,26 @@ def import_data(import_filepath, data_format='pickle'): return dataIN #Import settings -def import_settings(): - return +def import_settings(settings_import_path, settings_import_type='instrument', verbose=False): + + allList = ['all', ':', 'both', 'any'] + if settings_import_type.lower() not in allList: + # if just a single settings dict is desired + with open(settings_import_path, 'r') as f: + settingsDict = json.load(f) + else: + # Either a directory or list + if isinstance(settings_import_path, (list, tuple)): + for setPath in settings_import_path: + pass + else: + settings_import_path = sprit_utils.checkifpath(settings_import_path) + if not settings_import_path.is_dir(): + raise RuntimeError(f'settings_import_type={settings_import_type}, but settings_import_path is not list/tuple or filepath to directory') + else: + instFile = settings_import_path.glob('*.inst') + procFile = settings_import_path.glob('*.proc') + return settingsDict #Define input parameters def input_params(datapath, @@ -2332,10 +2374,9 @@ def input_params(datapath, elev_unit = 'feet', depth = 0, instrument = 'Raspberry Shake', - metapath = '', + metapath = None, hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], - instrument_settings=None, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -2380,16 +2421,12 @@ def input_params(datapath, Depth of seismometer. Not currently used, but will likely be used in the future. instrument : str or list {'Raspberry Shake') Instrument from which the data was acquired. - metapath : str or pathlib.Path object, default='' - Filepath of metadata, in format supported by obspy.read_inventory. If default value of '', will read from resources folder of repository (only supported for Raspberry Shake). + metapath : str or pathlib.Path object, default=None + Filepath of metadata, in format supported by obspy.read_inventory. If default value of None, will read from resources folder of repository (only supported for Raspberry Shake). hvsr_band : list, default=[0.4, 40] Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. - instrument_settings : None, str, default=None - The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. - If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). - The default settings can be reset using the save_settings() function with the parameter settings_path='default'. verbose : bool, default=False Whether to print output and results to terminal @@ -2410,21 +2447,6 @@ def input_params(datapath, print('\t {}={}'.format(key, value)) print() - #Make Sure metapath is all good - if not pathlib.Path(metapath).exists() or metapath=='': - if metapath == '': - pass - else: - print('Specified metadata file cannot be read!') - repoDir = pathlib.Path(os.path.dirname(__file__)) - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')) - #print('Using default metadata file for Raspberry Shake v.7 distributed with package') - else: - if isinstance(metapath, pathlib.PurePath): - metapath = metapath.as_posix() - else: - metapath = pathlib.Path(metapath).as_posix() - #Reformat times if type(acq_date) is datetime.datetime: date = str(acq_date.date()) @@ -2507,12 +2529,6 @@ def input_params(datapath, acq_date = datetime.date(year=int(date.split('-')[0]), month=int(date.split('-')[1]), day=int(date.split('-')[2])) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] - #Raspberry shake stationxml is in the resources folder, double check we have right path - if instrument.lower() in raspShakeInstNameList: - if metapath == r'resources/rs3dv7_metadata.inv': - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv7_metadata.inv')) - #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') - if output_crs is None: output_crs='EPSG:4326' @@ -2534,17 +2550,20 @@ def input_params(datapath, } #Replace any default parameter settings with those from json file of interest, potentially - if instrument_settings is None: - instrument_settings_dict = {} - elif instrument_settings == "default" or instrument_settings is True: - # Update inputParamDict with default file - default_settings_json = settings_dir.joinpath('instrument_settings.json') - with open(default_settings_json.as_posix(), 'r') as f: - instrument_settings_dict = json.load(f) - else: - # Update inputParamDict with file - with open(instrument_settings, 'r') as f: - instrument_settings_dict = json.load(f) + instrument_settings_dict = {} + if pathlib.Path(instrument).exists(): + instrument_settings = import_settings(settings_import_path=instrument, export_settings_type='instrument', verbose=verbose) + for k, settings_value in instrument_settings.items(): + if k in inspect.getfullargspec(input_params).args: + instrument_settings_dict[k] = settings_value + inputParamDict['instrument_settings'] = inputParamDict['instrument'] + inputParamDict.update(instrument_settings_dict) + + if instrument.lower() in raspShakeInstNameList: + if metapath is None: + metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')).as_posix() + inputParamDict['metapath'] = metapath + #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') for settingName in instrument_settings_dict.keys(): if settingName in inputParamDict.keys(): From 0883acb4c34593ebc8e334ae95c19d824ef7566d Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:37:50 -0500 Subject: [PATCH 14/17] minor updates to settings read and plot --- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 177698 -> 178113 bytes sprit/sprit_hvsr.py | 337 ++++++++++--------- 2 files changed, 169 insertions(+), 168 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 0576e4ea207101cc24bf98dc59a8a761f5c0c249..147277d8159f9fcffdc09b9bf589d7c401561e3a 100644 GIT binary patch delta 12479 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 diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index 6da8dea..431f553 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -2146,171 +2146,6 @@ def report_output(_report_format, _plot_type='HVSR p ann C+ p ann Spec', _return hvsr_results = report_output(_report_format=rep_form, _plot_type=plot_type, _return_results=return_results, _export_path=exp_path, _no_output=no_output, verbose=verbose) return hvsr_results -#Main function for plotting results -def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs): - """Function to plot HVSR data - - Parameters - ---------- - hvsr_data : dict - Dictionary containing output from process_hvsr function - plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC' - The plot_type of plot(s) to plot. If list, will plot all plots listed - - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below: - - 'p' shows a vertical dotted line at frequency of the "best" peak - - 'ann' annotates the frequency value of of the "best" peak - - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) - - 't' shows the H/V curve for all time windows - -'tp' shows all the peaks from the H/V curves of all the time windows - - 'COMP' - plot of the PPSD curves for each individual component ("C" also works) - - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR) - - 'p' shows a vertical dotted line at frequency of the "best" peak - - 'ann' annotates the frequency value of of the "best" peak - - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) - - 't' shows the H/V curve for all time windows - - 'SPEC' - spectrogram style plot of the H/V curve over time - - 'p' shows a horizontal dotted line at the frequency of the "best" peak - - 'ann' annotates the frequency value of the "best" peak - use_subplots : bool, default = True - Whether to output the plots as subplots (True) or as separate plots (False) - fig : matplotlib.Figure, default = None - If not None, matplotlib figure on which plot is plotted - ax : matplotlib.Axis, default = None - If not None, matplotlib axis on which plot is plotted - return_fig : bool - Whether to return figure and axis objects - save_dir : str or None - Directory in which to save figures - save_suffix : str - Suffix to add to end of figure filename(s), if save_dir is used - show_legend : bool, default=False - Whether to show legend in plot - show : bool - Whether to show plot - close_figs : bool, default=False - Whether to close figures before plotting - clear_fig : bool, default=True - Whether to clear figures before plotting - **kwargs : keyword arguments - Keyword arguments for matplotlib.pyplot - - Returns - ------- - fig, ax : matplotlib figure and axis objects - Returns figure and axis matplotlib.pyplot objects if return_fig=True, otherwise, simply plots the figures - """ - orig_args = locals().copy() #Get the initial arguments - if isinstance(hvsr_data, HVSRBatch): - #If running batch, we'll loop through each site - for site_name in hvsr_data.keys(): - args = orig_args.copy() #Make a copy so we don't accidentally overwrite - individual_params = hvsr_data[site_name] #Get what would normally be the "params" variable for each site - args['hvsr_results'] = individual_params #reset the params parameter we originally read in to an individual site params - if hvsr_data[site_name]['ProcessingStatus']['OverallStatus']: - try: - _hvsr_plot_batch(**args) #Call another function, that lets us run this function again - except: - print(f"{site_name} not able to be plotted.") - else: - if clear_fig and fig is not None and ax is not None: #Intended use for tkinter - #Clear everything - for key in ax: - ax[key].clear() - fig.clear() - if close_figs: - plt.close('all') - - compList = ['c', 'comp', 'component', 'components'] - specgramList = ['spec', 'specgram', 'spectrogram'] - hvsrList = ['hvsr', 'hv', 'h'] - - hvsrInd = np.nan - compInd = np.nan - specInd = np.nan - - kList = plot_type.split(' ') - for i, k in enumerate(kList): - kList[i] = k.lower() - - #Get the plots in the right order, no matter how they were input (and ensure the right options go with the right plot) - #HVSR index - if len(set(hvsrList).intersection(kList)): - for i, hv in enumerate(hvsrList): - if hv in kList: - hvsrInd = kList.index(hv) - break - #Component index - #if len(set(compList).intersection(kList)): - for i, c in enumerate(kList): - if '+' in c and c[:-1] in compList: - compInd = kList.index(c) - break - - #Specgram index - if len(set(specgramList).intersection(kList)): - for i, sp in enumerate(specgramList): - if sp in kList: - specInd = kList.index(sp) - break - - indList = [hvsrInd, compInd, specInd] - indListCopy = indList.copy() - plotTypeList = ['hvsr', 'comp', 'spec'] - - plotTypeOrder = [] - plotIndOrder = [] - - lastVal = 0 - while lastVal != 99: - firstInd = np.nanargmin(indListCopy) - plotTypeOrder.append(plotTypeList[firstInd]) - plotIndOrder.append(indList[firstInd]) - lastVal = indListCopy[firstInd] - indListCopy[firstInd] = 99 #just a high number - - plotTypeOrder.pop() - plotIndOrder[-1]=len(kList) - - for i, p in enumerate(plotTypeOrder): - pStartInd = plotIndOrder[i] - pEndInd = plotIndOrder[i+1] - plotComponents = kList[pStartInd:pEndInd] - - if use_subplots and i==0 and fig is None and ax is None: - mosaicPlots = [] - for pto in plotTypeOrder: - mosaicPlots.append([pto]) - fig, ax = plt.subplot_mosaic(mosaicPlots, gridspec_kw={'hspace':0.3}) - axis = ax[p] - elif use_subplots: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") #Often warns about xlim when it is not an issue - ax[p].clear() - axis = ax[p] - else: - fig, axis = plt.subplots() - - if p == 'hvsr': - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) - elif p=='comp': - plotComponents[0] = plotComponents[0][:-1] - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) - elif p=='spec': - plottypeKwargs = {} - for c in plotComponents: - plottypeKwargs[c] = True - kwargs.update(plottypeKwargs) - _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) - else: - warnings.warn('Plot type {p} not recognized', UserWarning) - - if show: - fig.canvas.draw() - - if return_fig: - return fig, ax - return - #Import data def import_data(import_filepath, data_format='pickle'): """Function to import .hvsr (or other extension) data exported using export_data() function @@ -2552,7 +2387,7 @@ def input_params(datapath, #Replace any default parameter settings with those from json file of interest, potentially instrument_settings_dict = {} if pathlib.Path(instrument).exists(): - instrument_settings = import_settings(settings_import_path=instrument, export_settings_type='instrument', verbose=verbose) + instrument_settings = import_settings(settings_import_path=instrument, settings_import_type='instrument', verbose=verbose) for k, settings_value in instrument_settings.items(): if k in inspect.getfullargspec(input_params).args: instrument_settings_dict[k] = settings_value @@ -2575,6 +2410,171 @@ def input_params(datapath, params = _check_processing_status(params) return params +#Main function for plotting results +def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs): + """Function to plot HVSR data + + Parameters + ---------- + hvsr_data : dict + Dictionary containing output from process_hvsr function + plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC' + The plot_type of plot(s) to plot. If list, will plot all plots listed + - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below: + - 'p' shows a vertical dotted line at frequency of the "best" peak + - 'ann' annotates the frequency value of of the "best" peak + - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) + - 't' shows the H/V curve for all time windows + -'tp' shows all the peaks from the H/V curves of all the time windows + - 'COMP' - plot of the PPSD curves for each individual component ("C" also works) + - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR) + - 'p' shows a vertical dotted line at frequency of the "best" peak + - 'ann' annotates the frequency value of of the "best" peak + - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) + - 't' shows the H/V curve for all time windows + - 'SPEC' - spectrogram style plot of the H/V curve over time + - 'p' shows a horizontal dotted line at the frequency of the "best" peak + - 'ann' annotates the frequency value of the "best" peak + use_subplots : bool, default = True + Whether to output the plots as subplots (True) or as separate plots (False) + fig : matplotlib.Figure, default = None + If not None, matplotlib figure on which plot is plotted + ax : matplotlib.Axis, default = None + If not None, matplotlib axis on which plot is plotted + return_fig : bool + Whether to return figure and axis objects + save_dir : str or None + Directory in which to save figures + save_suffix : str + Suffix to add to end of figure filename(s), if save_dir is used + show_legend : bool, default=False + Whether to show legend in plot + show : bool + Whether to show plot + close_figs : bool, default=False + Whether to close figures before plotting + clear_fig : bool, default=True + Whether to clear figures before plotting + **kwargs : keyword arguments + Keyword arguments for matplotlib.pyplot + + Returns + ------- + fig, ax : matplotlib figure and axis objects + Returns figure and axis matplotlib.pyplot objects if return_fig=True, otherwise, simply plots the figures + """ + orig_args = locals().copy() #Get the initial arguments + if isinstance(hvsr_data, HVSRBatch): + #If running batch, we'll loop through each site + for site_name in hvsr_data.keys(): + args = orig_args.copy() #Make a copy so we don't accidentally overwrite + individual_params = hvsr_data[site_name] #Get what would normally be the "params" variable for each site + args['hvsr_results'] = individual_params #reset the params parameter we originally read in to an individual site params + if hvsr_data[site_name]['ProcessingStatus']['OverallStatus']: + try: + _hvsr_plot_batch(**args) #Call another function, that lets us run this function again + except: + print(f"{site_name} not able to be plotted.") + else: + if clear_fig and fig is not None and ax is not None: #Intended use for tkinter + #Clear everything + for key in ax: + ax[key].clear() + fig.clear() + if close_figs: + plt.close('all') + + compList = ['c', 'comp', 'component', 'components'] + specgramList = ['spec', 'specgram', 'spectrogram'] + hvsrList = ['hvsr', 'hv', 'h'] + + hvsrInd = np.nan + compInd = np.nan + specInd = np.nan + + kList = plot_type.split(' ') + for i, k in enumerate(kList): + kList[i] = k.lower() + + #Get the plots in the right order, no matter how they were input (and ensure the right options go with the right plot) + #HVSR index + if len(set(hvsrList).intersection(kList)): + for i, hv in enumerate(hvsrList): + if hv in kList: + hvsrInd = kList.index(hv) + break + #Component index + #if len(set(compList).intersection(kList)): + for i, c in enumerate(kList): + if '+' in c and c[:-1] in compList: + compInd = kList.index(c) + break + + #Specgram index + if len(set(specgramList).intersection(kList)): + for i, sp in enumerate(specgramList): + if sp in kList: + specInd = kList.index(sp) + break + + indList = [hvsrInd, compInd, specInd] + indListCopy = indList.copy() + plotTypeList = ['hvsr', 'comp', 'spec'] + + plotTypeOrder = [] + plotIndOrder = [] + + lastVal = 0 + while lastVal != 99: + firstInd = np.nanargmin(indListCopy) + plotTypeOrder.append(plotTypeList[firstInd]) + plotIndOrder.append(indList[firstInd]) + lastVal = indListCopy[firstInd] + indListCopy[firstInd] = 99 #just a high number + + plotTypeOrder.pop() + plotIndOrder[-1]=len(kList) + + for i, p in enumerate(plotTypeOrder): + pStartInd = plotIndOrder[i] + pEndInd = plotIndOrder[i+1] + plotComponents = kList[pStartInd:pEndInd] + + if use_subplots and i==0 and fig is None and ax is None: + mosaicPlots = [] + for pto in plotTypeOrder: + mosaicPlots.append([pto]) + fig, ax = plt.subplot_mosaic(mosaicPlots, gridspec_kw={'hspace':0.3}) + axis = ax[p] + elif use_subplots: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") #Often warns about xlim when it is not an issue + ax[p].clear() + axis = ax[p] + else: + fig, axis = plt.subplots() + + if p == 'hvsr': + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) + elif p=='comp': + plotComponents[0] = plotComponents[0][:-1] + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) + elif p=='spec': + plottypeKwargs = {} + for c in plotComponents: + plottypeKwargs[c] = True + kwargs.update(plottypeKwargs) + _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) + else: + warnings.warn('Plot type {p} not recognized', UserWarning) + + if show: + fig.canvas.draw() + + if return_fig: + return fig, ax + return + #Plot Obspy Trace in axis using matplotlib def plot_stream(stream, params, fig=None, axes=None, show_plot=False, ylim_std=0.75, return_fig=True): """Function to plot a stream of data with Z, E, N components using matplotlib. Similar to obspy.Stream.Plot(), but will be formatted differently and eventually more customizable. @@ -5829,8 +5829,9 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix ax.axhline(hvsr_data['BestPeak']['f0'], c='k', linestyle='dotted', zorder=1000) if annotate: - xLocation = float(xmin) + (float(xmax)-float(xmin))*0.98 - ann = ax.text(x=xLocation, y=float(hvsr_data['BestPeak']['f0'])+0.3, fontsize='small', s=f"{hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va='bottom', + xLocation = float(xmin) + (float(xmax)-float(xmin))*0.99 + yLocation = hvsr_data['input_params']['hvsr_band'][0] + (hvsr_data['input_params']['hvsr_band'][1]-hvsr_data['input_params']['hvsr_band'][0])*(0.002) + ann = ax.text(x=xLocation, y=yLocation, fontsize='small', s=f"Peak at {hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va='bottom', bbox={'alpha':0.8, 'edgecolor':'w', 'fc':'w', 'pad':0.3}) ax.set_xlabel('UTC Time \n'+day) From 2f346e13a7b51f8278a17bb018e30c7dd8a1418f Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:43:07 -0500 Subject: [PATCH 15/17] instrument settings read in great now --- sprit/__pycache__/sprit_hvsr.cpython-310.pyc | Bin 178113 -> 178224 bytes sprit/sprit_hvsr.py | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-310.pyc b/sprit/__pycache__/sprit_hvsr.cpython-310.pyc index 147277d8159f9fcffdc09b9bf589d7c401561e3a..662c49ae3919cef70d510fa52ed2d533778ed388 100644 GIT binary patch delta 5058 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 diff --git a/sprit/sprit_hvsr.py b/sprit/sprit_hvsr.py index 431f553..442cb68 100644 --- a/sprit/sprit_hvsr.py +++ b/sprit/sprit_hvsr.py @@ -1080,7 +1080,6 @@ def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detr print('\nFetching data (fetch_data())') print() - print(params.items()) params = get_metadata(params, update_metadata=update_metadata, source=source) inv = params['inv'] date=params['acq_date'] @@ -2273,15 +2272,6 @@ def input_params(datapath, """ orig_args = locals().copy() #Get the initial arguments - #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment - global obspy - import obspy - if verbose: - print('Gathering input parameters (input_params())') - for key, value in orig_args.items(): - print('\t {}={}'.format(key, value)) - print() - #Reformat times if type(acq_date) is datetime.datetime: date = str(acq_date.date()) @@ -2388,8 +2378,11 @@ def input_params(datapath, instrument_settings_dict = {} if pathlib.Path(instrument).exists(): instrument_settings = import_settings(settings_import_path=instrument, settings_import_type='instrument', verbose=verbose) + input_params_args = inspect.getfullargspec(input_params).args + input_params_args.append('net') + input_params_args.append('sta') for k, settings_value in instrument_settings.items(): - if k in inspect.getfullargspec(input_params).args: + if k in input_params_args: instrument_settings_dict[k] = settings_value inputParamDict['instrument_settings'] = inputParamDict['instrument'] inputParamDict.update(instrument_settings_dict) @@ -2404,6 +2397,13 @@ def input_params(datapath, if settingName in inputParamDict.keys(): inputParamDict[settingName] = instrument_settings_dict[settingName] + #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment + if verbose: + print('Gathering input parameters (input_params())') + for key, value in inputParamDict.items(): + print('\t {}={}'.format(key, value)) + print() + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True @@ -5829,9 +5829,15 @@ def _plot_specgram_hvsr(hvsr_data, fig=None, ax=None, save_dir=None, save_suffix ax.axhline(hvsr_data['BestPeak']['f0'], c='k', linestyle='dotted', zorder=1000) if annotate: + if float(hvsr_data['BestPeak']['f0']) < 1: + boxYPerc = 0.998 + vertAlign = 'top' + else: + boxYPerc = 0.002 + vertAlign = 'bottom' xLocation = float(xmin) + (float(xmax)-float(xmin))*0.99 - yLocation = hvsr_data['input_params']['hvsr_band'][0] + (hvsr_data['input_params']['hvsr_band'][1]-hvsr_data['input_params']['hvsr_band'][0])*(0.002) - ann = ax.text(x=xLocation, y=yLocation, fontsize='small', s=f"Peak at {hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va='bottom', + yLocation = hvsr_data['input_params']['hvsr_band'][0] + (hvsr_data['input_params']['hvsr_band'][1]-hvsr_data['input_params']['hvsr_band'][0])*(boxYPerc) + ann = ax.text(x=xLocation, y=yLocation, fontsize='small', s=f"Peak at {hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va=vertAlign, bbox={'alpha':0.8, 'edgecolor':'w', 'fc':'w', 'pad':0.3}) ax.set_xlabel('UTC Time \n'+day) From d82ca80e1c26ac405e88d13c41763eff2192ab2d Mon Sep 17 00:00:00 2001 From: RJbalikian <46536937+RJbalikian@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:31:03 -0500 Subject: [PATCH 16/17] settings read in works, udpate vernum and docs --- conda/meta.yaml | 4 +- docs/generate_docs.py | 2 +- docs/main.html | 532 +++++-- docs/sprit_hvsr.html | 1359 +++++++++++------ pyproject.toml | 2 +- setup.py | 2 +- sprit/__pycache__/__init__.cpython-311.pyc | Bin 1731 -> 1813 bytes sprit/__pycache__/sprit_gui.cpython-311.pyc | Bin 215193 -> 217949 bytes sprit/__pycache__/sprit_hvsr.cpython-311.pyc | Bin 328277 -> 362863 bytes sprit/__pycache__/sprit_utils.cpython-311.pyc | Bin 18084 -> 18652 bytes .../settings/instrument_settings.inst | 10 + .../settings/instrument_settings.json | 12 - .../settings/processing_settings.json | 12 - .../settings/processing_settings.proc | 69 + sprit/sprit_hvsr.py | 177 ++- 15 files changed, 1477 insertions(+), 704 deletions(-) create mode 100644 sprit/resources/settings/instrument_settings.inst delete mode 100644 sprit/resources/settings/instrument_settings.json delete mode 100644 sprit/resources/settings/processing_settings.json create mode 100644 sprit/resources/settings/processing_settings.proc diff --git a/conda/meta.yaml b/conda/meta.yaml index af2ce6b..3cb9414 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,10 +1,10 @@ package: name: sprit - version: 0.1.51 + version: 0.1.50 source: git_url: https://github.com/RJbalikian/SPRIT-HVSR - git_tag: v0.1.51 + git_tag: v0.1.50 build: number: 0 diff --git a/docs/generate_docs.py b/docs/generate_docs.py index 5a34648..7383dae 100644 --- a/docs/generate_docs.py +++ b/docs/generate_docs.py @@ -8,7 +8,7 @@ #Whether to convert_md using markdown library (True), or let github do it (False) convert_md=True rtd_theme=False #Not currently working -release_version= '0.1.51' +release_version= '0.1.50' run_tests=True lint_it=True diff --git a/docs/main.html b/docs/main.html index 5690b38..322d7d7 100644 --- a/docs/main.html +++ b/docs/main.html @@ -39,7 +39,9 @@

    Package sprit

    from sprit.sprit_hvsr import( run, export_data, + export_settings, import_data, + import_settings, input_params, gui, get_metadata, @@ -49,7 +51,6 @@

    Package sprit

    process_hvsr, plot_hvsr, remove_noise, - save_settings, check_peaks, get_report, HVSRData, @@ -81,8 +82,10 @@

    Package sprit

    'get_char', 'time_it', 'checkifpath', - 'export_data', + 'export_data', + 'export_settings', 'import_data', + 'import_settings', 'input_params', 'gui', 'get_metadata', @@ -93,7 +96,6 @@

    Package sprit

    'process_hvsr', 'plot_hvsr', 'remove_noise', - 'save_settings', 'check_peaks', 'get_report', 'HVSRData', @@ -627,6 +629,21 @@

    Returns

    """ orig_args = locals().copy() #Get the initial arguments + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'check_peaks' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['check_peaks'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(check_peaks).args[1:], + inspect.getfullargspec(check_peaks).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + hvsr_band = orig_args['hvsr_band'] + peak_selection = orig_args['peak_selection'] + peak_freq_range = orig_args['peak_freq_range'] + verbose = orig_args['verbose'] + hvsr_data['processing_parameters']['check_peaks'] = {} for key, value in orig_args.items(): hvsr_data['processing_parameters']['check_peaks'][key] = value @@ -947,6 +964,139 @@

    Parameters

    return
    +
    +def export_settings(hvsr_data, export_settings_path='default', export_settings_type='all', include_location=False, verbose=False) +
    +
    +

    Save settings to json file

    +

    Parameters

    +
    +
    export_settings_path : str, default="default"
    +
    Where to save the json file(s) containing the settings, by default 'default'. +If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. +If 'all' is selected, a directory should be supplied. +Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    +
    export_settings_type : str, {'all', 'instrument', 'processing'}
    +
    What kind of settings to save. +If 'all', saves all possible types in their respective json files. +If 'instrument', save the instrument settings to their respective file. +If 'processing', saves the processing settings to their respective file. By default 'all'
    +
    include_location : bool, default=False, input CRS
    +
    Whether to include the location parametersin the exported settings document.This includes xcoord, ycoord, elevation, elev_unit, and input_crs
    +
    verbose : bool, default=True
    +
    Whether to print outputs and information to the terminal
    +
    +
    + +Expand source code + +
    def export_settings(hvsr_data, export_settings_path='default', export_settings_type='all', include_location=False, verbose=False):
    +    """Save settings to json file
    +
    +    Parameters
    +    ----------
    +    export_settings_path : str, default="default"
    +        Where to save the json file(s) containing the settings, by default 'default'. 
    +        If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to.
    +        If 'all' is selected, a directory should be supplied. 
    +        Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    +    export_settings_type : str, {'all', 'instrument', 'processing'}
    +        What kind of settings to save. 
    +        If 'all', saves all possible types in their respective json files.
    +        If 'instrument', save the instrument settings to their respective file.
    +        If 'processing', saves the processing settings to their respective file. By default 'all'
    +    include_location : bool, default=False, input CRS
    +        Whether to include the location parametersin the exported settings document.This includes xcoord, ycoord, elevation, elev_unit, and input_crs
    +    verbose : bool, default=True
    +        Whether to print outputs and information to the terminal
    +
    +    """
    +    fnameDict = {}
    +    fnameDict['instrument'] = "instrument_settings.json"
    +    fnameDict['processing'] = "processing_settings.json"
    +
    +    if export_settings_path == 'default' or export_settings_path is True:
    +        settingsPath = resource_dir.joinpath('settings')
    +    else:
    +        export_settings_path = pathlib.Path(export_settings_path)
    +        if not export_settings_path.exists():
    +            if not export_settings_path.parent.exists():
    +                print(f'The provided value for export_settings_path ({export_settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}')
    +                settingsPath = pathlib.Path.home()
    +            else:
    +                settingsPath = export_settings_path.parent
    +        
    +        if export_settings_path.is_dir():
    +            settingsPath = export_settings_path
    +        elif export_settings_path.is_file():
    +            settingsPath = export_settings_path.parent
    +            fnameDict['instrument'] = export_settings_path.name+"_instrumentSettings.json"
    +            fnameDict['processing'] = export_settings_path.name+"_processingSettings.json"
    +
    +    #Get final filepaths        
    +    instSetFPath = settingsPath.joinpath(fnameDict['instrument'])
    +    procSetFPath = settingsPath.joinpath(fnameDict['processing'])
    +
    +    #Get settings values
    +    instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"]
    +    inst_location_keys = ['xcoord', 'ycoord', 'elevation', 'elev_unit', 'input_crs']
    +    procFuncs = [fetch_data, remove_noise, generate_ppsds, process_hvsr, check_peaks, get_report]
    +
    +    instrument_settings_dict = {}
    +    processing_settings_dict = {}
    +
    +    for k in instKeys:
    +        if isinstance(hvsr_data[k], pathlib.PurePath):
    +            #For those that are paths and cannot be serialized
    +            instrument_settings_dict[k] = hvsr_data[k].as_posix()
    +        else:
    +            instrument_settings_dict[k] = hvsr_data[k]
    +
    +    if include_location:
    +        for k in inst_location_keys:
    +            if isinstance(hvsr_data[k], pathlib.PurePath):
    +                #For those that are paths and cannot be serialized
    +                instrument_settings_dict[k] = hvsr_data[k].as_posix()
    +            else:
    +                instrument_settings_dict[k] = hvsr_data[k]
    +
    +    
    +    for func in procFuncs:
    +        funcName = func.__name__
    +        processing_settings_dict[funcName] = {}
    +        for arg in hvsr_data['processing_parameters'][funcName]:
    +            if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)):
    +                pass
    +            else:
    +                processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg]
    +    
    +    #Save settings files
    +    if export_settings_type.lower()=='instrument' or export_settings_type.lower()=='all':
    +        with open(instSetFPath.with_suffix('.inst').as_posix(), 'w') as instSetF:
    +            jsonString = json.dumps(instrument_settings_dict, indent=2)
    +            #Format output for readability
    +            jsonString = jsonString.replace('\n    ', ' ')
    +            jsonString = jsonString.replace('[ ', '[')
    +            jsonString = jsonString.replace('\n  ]', ']')
    +            #Export
    +            instSetF.write(jsonString)
    +    if export_settings_type.lower()=='processing' or export_settings_type.lower()=='all':
    +        with open(procSetFPath.with_suffix('.proc').as_posix(), 'w') as procSetF:
    +            jsonString = json.dumps(processing_settings_dict, indent=2)
    +            #Format output for readability
    +            jsonString = jsonString.replace('\n    ', ' ')
    +            jsonString = jsonString.replace('[ ', '[')
    +            jsonString = jsonString.replace('\n  ]', ']')
    +            jsonString = jsonString.replace('\n  },','\n\t\t},\n')
    +            jsonString = jsonString.replace('{ "', '\n\t\t{\n\t\t"')
    +            jsonString = jsonString.replace(', "', ',\n\t\t"')
    +            jsonString = jsonString.replace('\n  }', '\n\t\t}')
    +            jsonString = jsonString.replace(': {', ':\n\t\t\t{')
    +               
    +            #Export
    +            procSetF.write(jsonString)
    +
    +
    def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detrend='spline', detrend_order=2, update_metadata=True, plot_input_stream=False, verbose=False, **kwargs)
    @@ -957,7 +1107,30 @@

    Parameters

    Expand source code
    def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detrend='spline', detrend_order=2, update_metadata=True, plot_input_stream=False, verbose=False, **kwargs):
    -    import warnings
    +    #Get intput paramaters
    +    orig_args = locals().copy()
    +    
    +    # Update with processing parameters specified previously in input_params, if applicable
    +    if 'processing_parameters' in params.keys():
    +        if 'fetch_data' in params['processing_parameters'].keys():
    +            defaultVDict = dict(zip(inspect.getfullargspec(fetch_data).args[1:], 
    +                        inspect.getfullargspec(fetch_data).defaults))
    +            defaultVDict['kwargs'] = kwargs
    +            for k, v in params['processing_parameters']['fetch_data'].items():
    +                # Manual input to function overrides the imported parameter values
    +                if k in orig_args.keys() and orig_args[k]==defaultVDict[k]:
    +                    orig_args[k] = v
    +
    +    #Update local variables, in case of previously-specified parameters
    +    source=orig_args['source']
    +    trim_dir=orig_args['trim_dir']
    +    export_format=orig_args['export_format']
    +    detrend=orig_args['detrend']
    +    detrend_order=orig_args['detrend_order']
    +    update_metadata=orig_args['update_metadata']
    +    plot_input_stream=orig_args['plot_input_stream']
    +    verbose=orig_args['verbose']
    +    kwargs=orig_args['kwargs']
     
         """Fetch ambient seismic data from a source to read into obspy stream
             
    @@ -1343,6 +1516,14 @@ 

    Parameters

    params['batch'] = False #Set False by default, will get corrected later in batch mode params['input_stream'] = dataIN.copy() params['stream'] = dataIN.copy() + + if 'processing_parameters' not in params.keys(): + params['processing_parameters'] = {} + params['processing_parameters']['fetch_data'] = {} + for key, value in orig_args.items(): + params['processing_parameters']['fetch_data'][key] = value + + params['ProcessingStatus']['FetchDataStatus'] = True if verbose and not isinstance(params, HVSRBatch): dataINStr = dataIN.__str__().split('\n') @@ -1630,7 +1811,24 @@

    Returns

    ppsd_kwargs = get_default_args(PPSD) ppsd_kwargs.update(ppsd_kwargs_sprit_defaults)#Update with sprit defaults, or user input - orig_args['ppsd_kwargs'] = [ppsd_kwargs] + orig_args['ppsd_kwargs'] = ppsd_kwargs + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'generate_ppsds' in hvsr_data['processing_parameters'].keys(): + defaultVDict = dict(zip(inspect.getfullargspec(generate_ppsds).args[1:], + inspect.getfullargspec(generate_ppsds).defaults)) + defaultVDict['ppsd_kwargs'] = ppsd_kwargs + for k, v in hvsr_data['processing_parameters']['generate_ppsds'].items(): + + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + remove_outliers = orig_args['remove_outliers'] + outlier_std = orig_args['outlier_std'] + verbose = orig_args['verbose'] + ppsd_kwargs = orig_args['ppsd_kwargs'] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: @@ -1825,7 +2023,8 @@

    Returns

    hvsr_data = sprit_utils.make_it_classy(hvsr_data) - hvsr_data['processing_parameters'] = {} + if 'processing_parameters' not in hvsr_data.keys(): + hvsr_data['processing_parameters'] = {} hvsr_data['processing_parameters']['generate_ppsds'] = {} for key, value in orig_args.items(): hvsr_data['processing_parameters']['generate_ppsds'][key] = value @@ -2052,11 +2251,26 @@

    Returns

    list/tuple - a list or tuple of the above objects, in the same order they are in the report_format list """ - #print statement - #Check if results are good - #Curve pass? orig_args = locals().copy() #Get the initial arguments + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_results.keys(): + if 'get_report' in hvsr_results['processing_parameters'].keys(): + for k, v in hvsr_results['processing_parameters']['get_report'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(get_report).args[1:], + inspect.getfullargspec(get_report).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + report_format = orig_args['report_format'] + plot_type = orig_args['plot_type'] + export_path = orig_args['export_path'] + return_results = orig_args['return_results'] + csv_overwrite_opt = orig_args['csv_overwrite_opt'] + no_output = orig_args['no_output'] + verbose = orig_args['verbose'] + hvsr_results['processing_parameters']['get_report'] = {} for key, value in orig_args.items(): hvsr_results['processing_parameters']['get_report'][key] = value @@ -2495,8 +2709,39 @@

    Returns

    return dataIN
    +
    +def import_settings(settings_import_path, settings_import_type='instrument', verbose=False) +
    +
    +
    +
    + +Expand source code + +
    def import_settings(settings_import_path, settings_import_type='instrument', verbose=False):
    +
    +    allList = ['all', ':', 'both', 'any']
    +    if settings_import_type.lower() not in allList:
    +        # if just a single settings dict is desired
    +        with open(settings_import_path, 'r') as f:
    +            settingsDict = json.load(f)
    +    else:
    +        # Either a directory or list
    +        if isinstance(settings_import_path, (list, tuple)):
    +            for setPath in settings_import_path:
    +                pass
    +        else:
    +            settings_import_path = sprit_utils.checkifpath(settings_import_path)
    +            if not settings_import_path.is_dir():
    +                raise RuntimeError(f'settings_import_type={settings_import_type}, but settings_import_path is not list/tuple or filepath to directory')
    +            else:
    +                instFile = settings_import_path.glob('*.inst')
    +                procFile = settings_import_path.glob('*.proc')
    +    return settingsDict
    +
    +
    -def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-28', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath='', hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], instrument_settings=None, verbose=False) +def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-30', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath=None, hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], processing_parameters={}, verbose=False)

    Function for designating input parameters for reading in and processing data

    @@ -2540,16 +2785,19 @@

    Parameters

    Depth of seismometer. Not currently used, but will likely be used in the future.
    instrument : str or list {'Raspberry Shake')
    Instrument from which the data was acquired.
    -
    metapath : str or pathlib.Path object, default=''
    -
    Filepath of metadata, in format supported by obspy.read_inventory. If default value of '', will read from resources folder of repository (only supported for Raspberry Shake).
    +
    metapath : str or pathlib.Path object, default=None
    +
    Filepath of metadata, in format supported by obspy.read_inventory. If default value of None, will read from resources folder of repository (only supported for Raspberry Shake).
    hvsr_band : list, default=[0.4, 40]
    Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later.
    peak_freq_range : list or tuple, default=[0.4, 40]
    Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range.
    -
    instrument_settings : None, str, default=None
    -
    The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. -If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). -The default settings can be reset using the save_settings() function with the parameter settings_path='default'.
    +
    processing_parameters={} : dict or filepath, default={}
    +
    If filepath, should point to a .proc json file with processing parameters (i.e, an output from sprit.export_settings()).
    +
    Note that this only applies to parameters for the functions: 'fetch_data', 'remove_noise', 'generate_ppsds', 'process_hvsr', 'check_peaks', and 'get_report.'
    +
    If dictionary, dictionary containing nested dictionaries of function names as they key, and the parameter names/values as key/value pairs for each key.
    +
    If a function name is not present, or if a parameter name is not present, default values will be used.
    +
    For example:
    +
    { 'fetch_data' : {'source':'batch', 'trim_dir':"/path/to/trimmed/data", 'export_format':'mseed', 'detrend':'spline', 'plot_input_stream':True, 'verbose':False, kwargs:{'kwargskey':'kwargsvalue'}} }
    verbose : bool, default=False
    Whether to print output and results to terminal
    @@ -2580,10 +2828,10 @@

    Returns

    elev_unit = 'feet', depth = 0, instrument = 'Raspberry Shake', - metapath = '', + metapath = None, hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], - instrument_settings=None, + processing_parameters={}, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -2628,16 +2876,19 @@

    Returns

    Depth of seismometer. Not currently used, but will likely be used in the future. instrument : str or list {'Raspberry Shake') Instrument from which the data was acquired. - metapath : str or pathlib.Path object, default='' - Filepath of metadata, in format supported by obspy.read_inventory. If default value of '', will read from resources folder of repository (only supported for Raspberry Shake). + metapath : str or pathlib.Path object, default=None + Filepath of metadata, in format supported by obspy.read_inventory. If default value of None, will read from resources folder of repository (only supported for Raspberry Shake). hvsr_band : list, default=[0.4, 40] Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. - instrument_settings : None, str, default=None - The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. - If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). - The default settings can be reset using the save_settings() function with the parameter settings_path='default'. + processing_parameters={} : dict or filepath, default={} + If filepath, should point to a .proc json file with processing parameters (i.e, an output from sprit.export_settings()). + Note that this only applies to parameters for the functions: 'fetch_data', 'remove_noise', 'generate_ppsds', 'process_hvsr', 'check_peaks', and 'get_report.' + If dictionary, dictionary containing nested dictionaries of function names as they key, and the parameter names/values as key/value pairs for each key. + If a function name is not present, or if a parameter name is not present, default values will be used. + For example: + `{ 'fetch_data' : {'source':'batch', 'trim_dir':"/path/to/trimmed/data", 'export_format':'mseed', 'detrend':'spline', 'plot_input_stream':True, 'verbose':False, kwargs:{'kwargskey':'kwargsvalue'}} }` verbose : bool, default=False Whether to print output and results to terminal @@ -2649,30 +2900,6 @@

    Returns

    """ orig_args = locals().copy() #Get the initial arguments - #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment - global obspy - import obspy - if verbose: - print('Gathering input parameters (input_params())') - for key, value in orig_args.items(): - print('\t {}={}'.format(key, value)) - print() - - #Make Sure metapath is all good - if not pathlib.Path(metapath).exists() or metapath=='': - if metapath == '': - pass - else: - print('Specified metadata file cannot be read!') - repoDir = pathlib.Path(os.path.dirname(__file__)) - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')) - #print('Using default metadata file for Raspberry Shake v.7 distributed with package') - else: - if isinstance(metapath, pathlib.PurePath): - metapath = metapath.as_posix() - else: - metapath = pathlib.Path(metapath).as_posix() - #Reformat times if type(acq_date) is datetime.datetime: date = str(acq_date.date()) @@ -2755,12 +2982,6 @@

    Returns

    acq_date = datetime.date(year=int(date.split('-')[0]), month=int(date.split('-')[1]), day=int(date.split('-')[2])) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] - #Raspberry shake stationxml is in the resources folder, double check we have right path - if instrument.lower() in raspShakeInstNameList: - if metapath == r'resources/rs3dv7_metadata.inv': - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv7_metadata.inv')) - #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') - if output_crs is None: output_crs='EPSG:4326' @@ -2782,22 +3003,41 @@

    Returns

    } #Replace any default parameter settings with those from json file of interest, potentially - if instrument_settings is None: - instrument_settings_dict = {} - elif instrument_settings == "default" or instrument_settings is True: - # Update inputParamDict with default file - default_settings_json = settings_dir.joinpath('instrument_settings.json') - with open(default_settings_json.as_posix(), 'r') as f: - instrument_settings_dict = json.load(f) - else: - # Update inputParamDict with file - with open(instrument_settings, 'r') as f: - instrument_settings_dict = json.load(f) + instrument_settings_dict = {} + if pathlib.Path(instrument).exists(): + instrument_settings = import_settings(settings_import_path=instrument, settings_import_type='instrument', verbose=verbose) + input_params_args = inspect.getfullargspec(input_params).args + input_params_args.append('net') + input_params_args.append('sta') + for k, settings_value in instrument_settings.items(): + if k in input_params_args: + instrument_settings_dict[k] = settings_value + inputParamDict['instrument_settings'] = inputParamDict['instrument'] + inputParamDict.update(instrument_settings_dict) + + if instrument.lower() in raspShakeInstNameList: + if metapath is None: + metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')).as_posix() + inputParamDict['metapath'] = metapath + #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') for settingName in instrument_settings_dict.keys(): if settingName in inputParamDict.keys(): inputParamDict[settingName] = instrument_settings_dict[settingName] + #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment + if verbose: + print('Gathering input parameters (input_params())') + for key, value in inputParamDict.items(): + print('\t {}={}'.format(key, value)) + print() + + if isinstance(processing_parameters, dict): + inputParamDict['processing_parameters'] = processing_parameters + else: + processing_parameters = sprit_utils.checkifpath(processing_parameters) + inputParamDict['processing_parameters'] = import_settings(processing_parameters, settings_import_type='processing', verbose=verbose) + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True @@ -3044,7 +3284,6 @@

    Returns

    elif p=='spec': plottypeKwargs = {} for c in plotComponents: - print(c) plottypeKwargs[c] = True kwargs.update(plottypeKwargs) _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) @@ -3167,6 +3406,27 @@

    Returns

    """ orig_args = locals().copy() #Get the initial arguments + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'process_hvsr' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['process_hvsr'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(process_hvsr).args[1:], + inspect.getfullargspec(process_hvsr).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + method = orig_args['method'] + smooth = orig_args['smooth'] + freq_smooth = orig_args['freq_smooth'] + f_smooth_width = orig_args['f_smooth_width'] + resample = orig_args['resample'] + outlier_curve_std = orig_args['outlier_curve_std'] + verbose = orig_args['verbose'] + + + if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: pass @@ -3612,7 +3872,30 @@

    Returns

    output : dict Dictionary similar to hvsr_data, but containing modified data with 'noise' removed """ - orig_args = locals().copy() #Get the initial arguments + #Get intput paramaters + orig_args = locals().copy() + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'remove_noise' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['remove_noise'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(remove_noise).args[1:], + inspect.getfullargspec(remove_noise).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + remove_method = orig_args['remove_method'] + sat_percent = orig_args['sat_percent'] + noise_percent = orig_args['noise_percent'] + sta = orig_args['sta'] + lta = orig_args['lta'] + stalta_thresh = orig_args['stalta_thresh'] + warmup_time = orig_args['warmup_time'] + cooldown_time = orig_args['cooldown_time'] + min_win_size = orig_args['min_win_size'] + remove_raw_noise = orig_args['remove_raw_noise'] + verbose = orig_args['verbose'] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: @@ -3734,6 +4017,13 @@

    Returns

    else: output['stream'] = outStream['stream'] output['input_stream'] = hvsr_data['input_stream'] + + if 'processing_parameters' not in output.keys(): + output['processing_parameters'] = {} + output['processing_parameters']['remove_noise'] = {} + for key, value in orig_args.items(): + output['processing_parameters']['remove_noise'][key] = value + output['ProcessingStatus']['RemoveNoiseStatus'] = True output = _check_processing_status(output) @@ -3759,13 +4049,16 @@

    Returns

    trEndTime = trace.stats.endtime outStream.merge() - output['stream'] = outStream + output['stream'] = outStream + elif isinstance(hvsr_data, obspy.core.stream.Stream) or isinstance(hvsr_data, obspy.core.trace.Trace): output = outStream else: warnings.warn(f"Output of type {type(output)} for this function will likely result in errors in other processing steps. Returning hvsr_data data.") return hvsr_data + + output = sprit_utils.make_it_classy(output) if 'xwindows_out' not in output.keys(): output['xwindows_out'] = [] @@ -4017,106 +4310,6 @@

    Raises

    return hvsr_results
    -
    -def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False) -
    -
    -

    Save settings to json file

    -

    Parameters

    -
    -
    settings_path : str, default="default"
    -
    Where to save the json file(s) containing the settings, by default 'default'. -If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. -If 'all' is selected, a directory should be supplied. -Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    -
    settings_type : str, {'all', 'instrument', 'processing'}
    -
    What kind of settings to save. -If 'all', saves all possible types in their respective json files. -If 'instrument', save the instrument settings to their respective file. -If 'processing', saves the processing settings to their respective file. By default 'all'
    -
    verbose : bool, default=True
    -
    Whether to print outputs and information to the terminal
    -
    -
    - -Expand source code - -
    def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False):
    -    """Save settings to json file
    -
    -    Parameters
    -    ----------
    -    settings_path : str, default="default"
    -        Where to save the json file(s) containing the settings, by default 'default'. 
    -        If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to.
    -        If 'all' is selected, a directory should be supplied. 
    -        Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    -    settings_type : str, {'all', 'instrument', 'processing'}
    -        What kind of settings to save. 
    -        If 'all', saves all possible types in their respective json files.
    -        If 'instrument', save the instrument settings to their respective file.
    -        If 'processing', saves the processing settings to their respective file. By default 'all'
    -    verbose : bool, default=True
    -        Whether to print outputs and information to the terminal
    -
    -    """
    -    fnameDict = {}
    -    fnameDict['instrument'] = "instrument_settings.json"
    -    fnameDict['processing'] = "processing_settings.json"
    -
    -    if settings_path == 'default' or settings_path is True:
    -        settingsPath = resource_dir.joinpath('settings')
    -    else:
    -        settings_path = pathlib.Path(settings_path)
    -        if not settings_path.exists():
    -            if not settings_path.parent.exists():
    -                print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}')
    -                settingsPath = pathlib.Path.home()
    -            else:
    -                settingsPath = settings_path.parent
    -        
    -        if settings_path.is_dir():
    -            settingsPath = settings_path
    -        elif settings_path.is_file():
    -            settingsPath = settings_path.parent
    -            fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json"
    -            fnameDict['processing'] = settings_path.name+"_processingSettings.json"
    -
    -    #Get final filepaths        
    -    instSetFPath = settingsPath.joinpath(fnameDict['instrument'])
    -    procSetFPath = settingsPath.joinpath(fnameDict['processing'])
    -
    -    #Get settings values
    -    instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"]
    -    procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report]
    -
    -    instrument_settings_dict = {}
    -    processing_settings_dict = {}
    -
    -    for k in instKeys:
    -        if isinstance(hvsr_data[k], pathlib.PurePath):
    -            instrument_settings_dict[k] = hvsr_data[k].as_posix()
    -        else:
    -            instrument_settings_dict[k] = hvsr_data[k]
    -    
    -    for func in procFuncs:
    -        funcName = func.__name__
    -        processing_settings_dict[funcName] = {}
    -        for arg in inspect.getfullargspec(func)[0]:
    -            if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)):
    -                pass
    -            else:
    -                processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg]
    -    
    -    #Save settings files
    -    if settings_type.lower()=='instrument' or settings_type.lower()=='all':
    -        with open(instSetFPath.as_posix(), 'w') as instSetF:
    -            json.dump(instrument_settings_dict, instSetF)
    -    if settings_type.lower()=='processing' or settings_type.lower()=='all':
    -        with open(procSetFPath.as_posix(), 'w') as procSetF:
    -            json.dump(processing_settings_dict, procSetF)        
    -
    -
    def time_it(_t, proc_name='', verbose=True)
    @@ -5063,6 +5256,7 @@

    Index

  • check_xvalues
  • checkifpath
  • export_data
  • +
  • export_settings
  • fetch_data
  • format_time
  • generate_ppsds
  • @@ -5072,6 +5266,7 @@

    Index

  • gui
  • has_required_channels
  • import_data
  • +
  • import_settings
  • input_params
  • make_it_classy
  • plot_hvsr
  • @@ -5079,7 +5274,6 @@

    Index

  • read_from_RS
  • remove_noise
  • run
  • -
  • save_settings
  • time_it
  • diff --git a/docs/sprit_hvsr.html b/docs/sprit_hvsr.html index cff6f14..85f70b8 100644 --- a/docs/sprit_hvsr.html +++ b/docs/sprit_hvsr.html @@ -46,11 +46,11 @@

    Module sprit.sprit_hvsr

    import pickle import pkg_resources import struct +import sys import tempfile import traceback import warnings import xml.etree.ElementTree as ET -import sys import matplotlib from matplotlib.backend_bases import MouseButton @@ -759,6 +759,21 @@

    Module sprit.sprit_hvsr

    """ orig_args = locals().copy() #Get the initial arguments + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'check_peaks' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['check_peaks'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(check_peaks).args[1:], + inspect.getfullargspec(check_peaks).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + hvsr_band = orig_args['hvsr_band'] + peak_selection = orig_args['peak_selection'] + peak_freq_range = orig_args['peak_freq_range'] + verbose = orig_args['verbose'] + hvsr_data['processing_parameters']['check_peaks'] = {} for key, value in orig_args.items(): hvsr_data['processing_parameters']['check_peaks'][key] = value @@ -959,9 +974,140 @@

    Module sprit.sprit_hvsr

    print("Error in data export. Data must be either of type sprit.HVSRData or sprit.HVSRBatch") return +###WORKING ON THIS +#Save default instrument and processing settings to json file(s) +def export_settings(hvsr_data, export_settings_path='default', export_settings_type='all', include_location=False, verbose=False): + """Save settings to json file + + Parameters + ---------- + export_settings_path : str, default="default" + Where to save the json file(s) containing the settings, by default 'default'. + If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. + If 'all' is selected, a directory should be supplied. + Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory. + export_settings_type : str, {'all', 'instrument', 'processing'} + What kind of settings to save. + If 'all', saves all possible types in their respective json files. + If 'instrument', save the instrument settings to their respective file. + If 'processing', saves the processing settings to their respective file. By default 'all' + include_location : bool, default=False, input CRS + Whether to include the location parametersin the exported settings document.This includes xcoord, ycoord, elevation, elev_unit, and input_crs + verbose : bool, default=True + Whether to print outputs and information to the terminal + + """ + fnameDict = {} + fnameDict['instrument'] = "instrument_settings.json" + fnameDict['processing'] = "processing_settings.json" + + if export_settings_path == 'default' or export_settings_path is True: + settingsPath = resource_dir.joinpath('settings') + else: + export_settings_path = pathlib.Path(export_settings_path) + if not export_settings_path.exists(): + if not export_settings_path.parent.exists(): + print(f'The provided value for export_settings_path ({export_settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}') + settingsPath = pathlib.Path.home() + else: + settingsPath = export_settings_path.parent + + if export_settings_path.is_dir(): + settingsPath = export_settings_path + elif export_settings_path.is_file(): + settingsPath = export_settings_path.parent + fnameDict['instrument'] = export_settings_path.name+"_instrumentSettings.json" + fnameDict['processing'] = export_settings_path.name+"_processingSettings.json" + + #Get final filepaths + instSetFPath = settingsPath.joinpath(fnameDict['instrument']) + procSetFPath = settingsPath.joinpath(fnameDict['processing']) + + #Get settings values + instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"] + inst_location_keys = ['xcoord', 'ycoord', 'elevation', 'elev_unit', 'input_crs'] + procFuncs = [fetch_data, remove_noise, generate_ppsds, process_hvsr, check_peaks, get_report] + + instrument_settings_dict = {} + processing_settings_dict = {} + + for k in instKeys: + if isinstance(hvsr_data[k], pathlib.PurePath): + #For those that are paths and cannot be serialized + instrument_settings_dict[k] = hvsr_data[k].as_posix() + else: + instrument_settings_dict[k] = hvsr_data[k] + + if include_location: + for k in inst_location_keys: + if isinstance(hvsr_data[k], pathlib.PurePath): + #For those that are paths and cannot be serialized + instrument_settings_dict[k] = hvsr_data[k].as_posix() + else: + instrument_settings_dict[k] = hvsr_data[k] + + + for func in procFuncs: + funcName = func.__name__ + processing_settings_dict[funcName] = {} + for arg in hvsr_data['processing_parameters'][funcName]: + if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)): + pass + else: + processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg] + + #Save settings files + if export_settings_type.lower()=='instrument' or export_settings_type.lower()=='all': + with open(instSetFPath.with_suffix('.inst').as_posix(), 'w') as instSetF: + jsonString = json.dumps(instrument_settings_dict, indent=2) + #Format output for readability + jsonString = jsonString.replace('\n ', ' ') + jsonString = jsonString.replace('[ ', '[') + jsonString = jsonString.replace('\n ]', ']') + #Export + instSetF.write(jsonString) + if export_settings_type.lower()=='processing' or export_settings_type.lower()=='all': + with open(procSetFPath.with_suffix('.proc').as_posix(), 'w') as procSetF: + jsonString = json.dumps(processing_settings_dict, indent=2) + #Format output for readability + jsonString = jsonString.replace('\n ', ' ') + jsonString = jsonString.replace('[ ', '[') + jsonString = jsonString.replace('\n ]', ']') + jsonString = jsonString.replace('\n },','\n\t\t},\n') + jsonString = jsonString.replace('{ "', '\n\t\t{\n\t\t"') + jsonString = jsonString.replace(', "', ',\n\t\t"') + jsonString = jsonString.replace('\n }', '\n\t\t}') + jsonString = jsonString.replace(': {', ':\n\t\t\t{') + + #Export + procSetF.write(jsonString) + #Reads in traces to obspy stream def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detrend='spline', detrend_order=2, update_metadata=True, plot_input_stream=False, verbose=False, **kwargs): - import warnings + #Get intput paramaters + orig_args = locals().copy() + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in params.keys(): + if 'fetch_data' in params['processing_parameters'].keys(): + defaultVDict = dict(zip(inspect.getfullargspec(fetch_data).args[1:], + inspect.getfullargspec(fetch_data).defaults)) + defaultVDict['kwargs'] = kwargs + for k, v in params['processing_parameters']['fetch_data'].items(): + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + #Update local variables, in case of previously-specified parameters + source=orig_args['source'] + trim_dir=orig_args['trim_dir'] + export_format=orig_args['export_format'] + detrend=orig_args['detrend'] + detrend_order=orig_args['detrend_order'] + update_metadata=orig_args['update_metadata'] + plot_input_stream=orig_args['plot_input_stream'] + verbose=orig_args['verbose'] + kwargs=orig_args['kwargs'] """Fetch ambient seismic data from a source to read into obspy stream @@ -1347,6 +1493,14 @@

    Module sprit.sprit_hvsr

    params['batch'] = False #Set False by default, will get corrected later in batch mode params['input_stream'] = dataIN.copy() params['stream'] = dataIN.copy() + + if 'processing_parameters' not in params.keys(): + params['processing_parameters'] = {} + params['processing_parameters']['fetch_data'] = {} + for key, value in orig_args.items(): + params['processing_parameters']['fetch_data'][key] = value + + params['ProcessingStatus']['FetchDataStatus'] = True if verbose and not isinstance(params, HVSRBatch): dataINStr = dataIN.__str__().split('\n') @@ -1413,7 +1567,24 @@

    Module sprit.sprit_hvsr

    ppsd_kwargs = get_default_args(PPSD) ppsd_kwargs.update(ppsd_kwargs_sprit_defaults)#Update with sprit defaults, or user input - orig_args['ppsd_kwargs'] = [ppsd_kwargs] + orig_args['ppsd_kwargs'] = ppsd_kwargs + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'generate_ppsds' in hvsr_data['processing_parameters'].keys(): + defaultVDict = dict(zip(inspect.getfullargspec(generate_ppsds).args[1:], + inspect.getfullargspec(generate_ppsds).defaults)) + defaultVDict['ppsd_kwargs'] = ppsd_kwargs + for k, v in hvsr_data['processing_parameters']['generate_ppsds'].items(): + + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + remove_outliers = orig_args['remove_outliers'] + outlier_std = orig_args['outlier_std'] + verbose = orig_args['verbose'] + ppsd_kwargs = orig_args['ppsd_kwargs'] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: @@ -1608,7 +1779,8 @@

    Module sprit.sprit_hvsr

    hvsr_data = sprit_utils.make_it_classy(hvsr_data) - hvsr_data['processing_parameters'] = {} + if 'processing_parameters' not in hvsr_data.keys(): + hvsr_data['processing_parameters'] = {} hvsr_data['processing_parameters']['generate_ppsds'] = {} for key, value in orig_args.items(): hvsr_data['processing_parameters']['generate_ppsds'][key] = value @@ -1746,11 +1918,26 @@

    Module sprit.sprit_hvsr

    list/tuple - a list or tuple of the above objects, in the same order they are in the report_format list """ - #print statement - #Check if results are good - #Curve pass? orig_args = locals().copy() #Get the initial arguments + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_results.keys(): + if 'get_report' in hvsr_results['processing_parameters'].keys(): + for k, v in hvsr_results['processing_parameters']['get_report'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(get_report).args[1:], + inspect.getfullargspec(get_report).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + report_format = orig_args['report_format'] + plot_type = orig_args['plot_type'] + export_path = orig_args['export_path'] + return_results = orig_args['return_results'] + csv_overwrite_opt = orig_args['csv_overwrite_opt'] + no_output = orig_args['no_output'] + verbose = orig_args['verbose'] + hvsr_results['processing_parameters']['get_report'] = {} for key, value in orig_args.items(): hvsr_results['processing_parameters']['get_report'][key] = value @@ -2070,172 +2257,6 @@

    Module sprit.sprit_hvsr

    hvsr_results = report_output(_report_format=rep_form, _plot_type=plot_type, _return_results=return_results, _export_path=exp_path, _no_output=no_output, verbose=verbose) return hvsr_results -#Main function for plotting results -def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs): - """Function to plot HVSR data - - Parameters - ---------- - hvsr_data : dict - Dictionary containing output from process_hvsr function - plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC' - The plot_type of plot(s) to plot. If list, will plot all plots listed - - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below: - - 'p' shows a vertical dotted line at frequency of the "best" peak - - 'ann' annotates the frequency value of of the "best" peak - - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) - - 't' shows the H/V curve for all time windows - -'tp' shows all the peaks from the H/V curves of all the time windows - - 'COMP' - plot of the PPSD curves for each individual component ("C" also works) - - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR) - - 'p' shows a vertical dotted line at frequency of the "best" peak - - 'ann' annotates the frequency value of of the "best" peak - - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) - - 't' shows the H/V curve for all time windows - - 'SPEC' - spectrogram style plot of the H/V curve over time - - 'p' shows a horizontal dotted line at the frequency of the "best" peak - - 'ann' annotates the frequency value of the "best" peak - use_subplots : bool, default = True - Whether to output the plots as subplots (True) or as separate plots (False) - fig : matplotlib.Figure, default = None - If not None, matplotlib figure on which plot is plotted - ax : matplotlib.Axis, default = None - If not None, matplotlib axis on which plot is plotted - return_fig : bool - Whether to return figure and axis objects - save_dir : str or None - Directory in which to save figures - save_suffix : str - Suffix to add to end of figure filename(s), if save_dir is used - show_legend : bool, default=False - Whether to show legend in plot - show : bool - Whether to show plot - close_figs : bool, default=False - Whether to close figures before plotting - clear_fig : bool, default=True - Whether to clear figures before plotting - **kwargs : keyword arguments - Keyword arguments for matplotlib.pyplot - - Returns - ------- - fig, ax : matplotlib figure and axis objects - Returns figure and axis matplotlib.pyplot objects if return_fig=True, otherwise, simply plots the figures - """ - orig_args = locals().copy() #Get the initial arguments - if isinstance(hvsr_data, HVSRBatch): - #If running batch, we'll loop through each site - for site_name in hvsr_data.keys(): - args = orig_args.copy() #Make a copy so we don't accidentally overwrite - individual_params = hvsr_data[site_name] #Get what would normally be the "params" variable for each site - args['hvsr_results'] = individual_params #reset the params parameter we originally read in to an individual site params - if hvsr_data[site_name]['ProcessingStatus']['OverallStatus']: - try: - _hvsr_plot_batch(**args) #Call another function, that lets us run this function again - except: - print(f"{site_name} not able to be plotted.") - else: - if clear_fig and fig is not None and ax is not None: #Intended use for tkinter - #Clear everything - for key in ax: - ax[key].clear() - fig.clear() - if close_figs: - plt.close('all') - - compList = ['c', 'comp', 'component', 'components'] - specgramList = ['spec', 'specgram', 'spectrogram'] - hvsrList = ['hvsr', 'hv', 'h'] - - hvsrInd = np.nan - compInd = np.nan - specInd = np.nan - - kList = plot_type.split(' ') - for i, k in enumerate(kList): - kList[i] = k.lower() - - #Get the plots in the right order, no matter how they were input (and ensure the right options go with the right plot) - #HVSR index - if len(set(hvsrList).intersection(kList)): - for i, hv in enumerate(hvsrList): - if hv in kList: - hvsrInd = kList.index(hv) - break - #Component index - #if len(set(compList).intersection(kList)): - for i, c in enumerate(kList): - if '+' in c and c[:-1] in compList: - compInd = kList.index(c) - break - - #Specgram index - if len(set(specgramList).intersection(kList)): - for i, sp in enumerate(specgramList): - if sp in kList: - specInd = kList.index(sp) - break - - indList = [hvsrInd, compInd, specInd] - indListCopy = indList.copy() - plotTypeList = ['hvsr', 'comp', 'spec'] - - plotTypeOrder = [] - plotIndOrder = [] - - lastVal = 0 - while lastVal != 99: - firstInd = np.nanargmin(indListCopy) - plotTypeOrder.append(plotTypeList[firstInd]) - plotIndOrder.append(indList[firstInd]) - lastVal = indListCopy[firstInd] - indListCopy[firstInd] = 99 #just a high number - - plotTypeOrder.pop() - plotIndOrder[-1]=len(kList) - - for i, p in enumerate(plotTypeOrder): - pStartInd = plotIndOrder[i] - pEndInd = plotIndOrder[i+1] - plotComponents = kList[pStartInd:pEndInd] - - if use_subplots and i==0 and fig is None and ax is None: - mosaicPlots = [] - for pto in plotTypeOrder: - mosaicPlots.append([pto]) - fig, ax = plt.subplot_mosaic(mosaicPlots, gridspec_kw={'hspace':0.3}) - axis = ax[p] - elif use_subplots: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") #Often warns about xlim when it is not an issue - ax[p].clear() - axis = ax[p] - else: - fig, axis = plt.subplots() - - if p == 'hvsr': - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) - elif p=='comp': - plotComponents[0] = plotComponents[0][:-1] - _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) - elif p=='spec': - plottypeKwargs = {} - for c in plotComponents: - print(c) - plottypeKwargs[c] = True - kwargs.update(plottypeKwargs) - _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) - else: - warnings.warn('Plot type {p} not recognized', UserWarning) - - if show: - fig.canvas.draw() - - if return_fig: - return fig, ax - return - #Import data def import_data(import_filepath, data_format='pickle'): """Function to import .hvsr (or other extension) data exported using export_data() function @@ -2258,6 +2279,28 @@

    Module sprit.sprit_hvsr

    dataIN = import_filepath return dataIN +#Import settings +def import_settings(settings_import_path, settings_import_type='instrument', verbose=False): + + allList = ['all', ':', 'both', 'any'] + if settings_import_type.lower() not in allList: + # if just a single settings dict is desired + with open(settings_import_path, 'r') as f: + settingsDict = json.load(f) + else: + # Either a directory or list + if isinstance(settings_import_path, (list, tuple)): + for setPath in settings_import_path: + pass + else: + settings_import_path = sprit_utils.checkifpath(settings_import_path) + if not settings_import_path.is_dir(): + raise RuntimeError(f'settings_import_type={settings_import_type}, but settings_import_path is not list/tuple or filepath to directory') + else: + instFile = settings_import_path.glob('*.inst') + procFile = settings_import_path.glob('*.proc') + return settingsDict + #Define input parameters def input_params(datapath, site='HVSR Site', @@ -2277,10 +2320,10 @@

    Module sprit.sprit_hvsr

    elev_unit = 'feet', depth = 0, instrument = 'Raspberry Shake', - metapath = '', + metapath = None, hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], - instrument_settings=None, + processing_parameters={}, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -2325,16 +2368,19 @@

    Module sprit.sprit_hvsr

    Depth of seismometer. Not currently used, but will likely be used in the future. instrument : str or list {'Raspberry Shake') Instrument from which the data was acquired. - metapath : str or pathlib.Path object, default='' - Filepath of metadata, in format supported by obspy.read_inventory. If default value of '', will read from resources folder of repository (only supported for Raspberry Shake). + metapath : str or pathlib.Path object, default=None + Filepath of metadata, in format supported by obspy.read_inventory. If default value of None, will read from resources folder of repository (only supported for Raspberry Shake). hvsr_band : list, default=[0.4, 40] Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. - instrument_settings : None, str, default=None - The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. - If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). - The default settings can be reset using the save_settings() function with the parameter settings_path='default'. + processing_parameters={} : dict or filepath, default={} + If filepath, should point to a .proc json file with processing parameters (i.e, an output from sprit.export_settings()). + Note that this only applies to parameters for the functions: 'fetch_data', 'remove_noise', 'generate_ppsds', 'process_hvsr', 'check_peaks', and 'get_report.' + If dictionary, dictionary containing nested dictionaries of function names as they key, and the parameter names/values as key/value pairs for each key. + If a function name is not present, or if a parameter name is not present, default values will be used. + For example: + `{ 'fetch_data' : {'source':'batch', 'trim_dir':"/path/to/trimmed/data", 'export_format':'mseed', 'detrend':'spline', 'plot_input_stream':True, 'verbose':False, kwargs:{'kwargskey':'kwargsvalue'}} }` verbose : bool, default=False Whether to print output and results to terminal @@ -2346,30 +2392,6 @@

    Module sprit.sprit_hvsr

    """ orig_args = locals().copy() #Get the initial arguments - #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment - global obspy - import obspy - if verbose: - print('Gathering input parameters (input_params())') - for key, value in orig_args.items(): - print('\t {}={}'.format(key, value)) - print() - - #Make Sure metapath is all good - if not pathlib.Path(metapath).exists() or metapath=='': - if metapath == '': - pass - else: - print('Specified metadata file cannot be read!') - repoDir = pathlib.Path(os.path.dirname(__file__)) - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')) - #print('Using default metadata file for Raspberry Shake v.7 distributed with package') - else: - if isinstance(metapath, pathlib.PurePath): - metapath = metapath.as_posix() - else: - metapath = pathlib.Path(metapath).as_posix() - #Reformat times if type(acq_date) is datetime.datetime: date = str(acq_date.date()) @@ -2452,12 +2474,6 @@

    Module sprit.sprit_hvsr

    acq_date = datetime.date(year=int(date.split('-')[0]), month=int(date.split('-')[1]), day=int(date.split('-')[2])) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] - #Raspberry shake stationxml is in the resources folder, double check we have right path - if instrument.lower() in raspShakeInstNameList: - if metapath == r'resources/rs3dv7_metadata.inv': - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv7_metadata.inv')) - #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') - if output_crs is None: output_crs='EPSG:4326' @@ -2479,28 +2495,212 @@

    Module sprit.sprit_hvsr

    } #Replace any default parameter settings with those from json file of interest, potentially - if instrument_settings is None: - instrument_settings_dict = {} - elif instrument_settings == "default" or instrument_settings is True: - # Update inputParamDict with default file - default_settings_json = settings_dir.joinpath('instrument_settings.json') - with open(default_settings_json.as_posix(), 'r') as f: - instrument_settings_dict = json.load(f) - else: - # Update inputParamDict with file - with open(instrument_settings, 'r') as f: - instrument_settings_dict = json.load(f) + instrument_settings_dict = {} + if pathlib.Path(instrument).exists(): + instrument_settings = import_settings(settings_import_path=instrument, settings_import_type='instrument', verbose=verbose) + input_params_args = inspect.getfullargspec(input_params).args + input_params_args.append('net') + input_params_args.append('sta') + for k, settings_value in instrument_settings.items(): + if k in input_params_args: + instrument_settings_dict[k] = settings_value + inputParamDict['instrument_settings'] = inputParamDict['instrument'] + inputParamDict.update(instrument_settings_dict) + + if instrument.lower() in raspShakeInstNameList: + if metapath is None: + metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')).as_posix() + inputParamDict['metapath'] = metapath + #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') for settingName in instrument_settings_dict.keys(): if settingName in inputParamDict.keys(): inputParamDict[settingName] = instrument_settings_dict[settingName] + #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment + if verbose: + print('Gathering input parameters (input_params())') + for key, value in inputParamDict.items(): + print('\t {}={}'.format(key, value)) + print() + + if isinstance(processing_parameters, dict): + inputParamDict['processing_parameters'] = processing_parameters + else: + processing_parameters = sprit_utils.checkifpath(processing_parameters) + inputParamDict['processing_parameters'] = import_settings(processing_parameters, settings_import_type='processing', verbose=verbose) + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True params = _check_processing_status(params) return params +#Main function for plotting results +def plot_hvsr(hvsr_data, plot_type='HVSR ann p C+ ann p SPEC', use_subplots=True, fig=None, ax=None, return_fig=False, save_dir=None, save_suffix='', show_legend=False, show=True, close_figs=False, clear_fig=True,**kwargs): + """Function to plot HVSR data + + Parameters + ---------- + hvsr_data : dict + Dictionary containing output from process_hvsr function + plot_type : str or list, default = 'HVSR ann p C+ ann p SPEC' + The plot_type of plot(s) to plot. If list, will plot all plots listed + - 'HVSR' - Standard HVSR plot, including standard deviation. Options are included below: + - 'p' shows a vertical dotted line at frequency of the "best" peak + - 'ann' annotates the frequency value of of the "best" peak + - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) + - 't' shows the H/V curve for all time windows + -'tp' shows all the peaks from the H/V curves of all the time windows + - 'COMP' - plot of the PPSD curves for each individual component ("C" also works) + - '+' (as a suffix in 'C+' or 'COMP+') plots C on a plot separate from HVSR (C+ is default, but without + will plot on the same plot as HVSR) + - 'p' shows a vertical dotted line at frequency of the "best" peak + - 'ann' annotates the frequency value of of the "best" peak + - 'all' shows all the peaks identified in check_peaks() (by default, only the max is identified) + - 't' shows the H/V curve for all time windows + - 'SPEC' - spectrogram style plot of the H/V curve over time + - 'p' shows a horizontal dotted line at the frequency of the "best" peak + - 'ann' annotates the frequency value of the "best" peak + use_subplots : bool, default = True + Whether to output the plots as subplots (True) or as separate plots (False) + fig : matplotlib.Figure, default = None + If not None, matplotlib figure on which plot is plotted + ax : matplotlib.Axis, default = None + If not None, matplotlib axis on which plot is plotted + return_fig : bool + Whether to return figure and axis objects + save_dir : str or None + Directory in which to save figures + save_suffix : str + Suffix to add to end of figure filename(s), if save_dir is used + show_legend : bool, default=False + Whether to show legend in plot + show : bool + Whether to show plot + close_figs : bool, default=False + Whether to close figures before plotting + clear_fig : bool, default=True + Whether to clear figures before plotting + **kwargs : keyword arguments + Keyword arguments for matplotlib.pyplot + + Returns + ------- + fig, ax : matplotlib figure and axis objects + Returns figure and axis matplotlib.pyplot objects if return_fig=True, otherwise, simply plots the figures + """ + orig_args = locals().copy() #Get the initial arguments + if isinstance(hvsr_data, HVSRBatch): + #If running batch, we'll loop through each site + for site_name in hvsr_data.keys(): + args = orig_args.copy() #Make a copy so we don't accidentally overwrite + individual_params = hvsr_data[site_name] #Get what would normally be the "params" variable for each site + args['hvsr_results'] = individual_params #reset the params parameter we originally read in to an individual site params + if hvsr_data[site_name]['ProcessingStatus']['OverallStatus']: + try: + _hvsr_plot_batch(**args) #Call another function, that lets us run this function again + except: + print(f"{site_name} not able to be plotted.") + else: + if clear_fig and fig is not None and ax is not None: #Intended use for tkinter + #Clear everything + for key in ax: + ax[key].clear() + fig.clear() + if close_figs: + plt.close('all') + + compList = ['c', 'comp', 'component', 'components'] + specgramList = ['spec', 'specgram', 'spectrogram'] + hvsrList = ['hvsr', 'hv', 'h'] + + hvsrInd = np.nan + compInd = np.nan + specInd = np.nan + + kList = plot_type.split(' ') + for i, k in enumerate(kList): + kList[i] = k.lower() + + #Get the plots in the right order, no matter how they were input (and ensure the right options go with the right plot) + #HVSR index + if len(set(hvsrList).intersection(kList)): + for i, hv in enumerate(hvsrList): + if hv in kList: + hvsrInd = kList.index(hv) + break + #Component index + #if len(set(compList).intersection(kList)): + for i, c in enumerate(kList): + if '+' in c and c[:-1] in compList: + compInd = kList.index(c) + break + + #Specgram index + if len(set(specgramList).intersection(kList)): + for i, sp in enumerate(specgramList): + if sp in kList: + specInd = kList.index(sp) + break + + indList = [hvsrInd, compInd, specInd] + indListCopy = indList.copy() + plotTypeList = ['hvsr', 'comp', 'spec'] + + plotTypeOrder = [] + plotIndOrder = [] + + lastVal = 0 + while lastVal != 99: + firstInd = np.nanargmin(indListCopy) + plotTypeOrder.append(plotTypeList[firstInd]) + plotIndOrder.append(indList[firstInd]) + lastVal = indListCopy[firstInd] + indListCopy[firstInd] = 99 #just a high number + + plotTypeOrder.pop() + plotIndOrder[-1]=len(kList) + + for i, p in enumerate(plotTypeOrder): + pStartInd = plotIndOrder[i] + pEndInd = plotIndOrder[i+1] + plotComponents = kList[pStartInd:pEndInd] + + if use_subplots and i==0 and fig is None and ax is None: + mosaicPlots = [] + for pto in plotTypeOrder: + mosaicPlots.append([pto]) + fig, ax = plt.subplot_mosaic(mosaicPlots, gridspec_kw={'hspace':0.3}) + axis = ax[p] + elif use_subplots: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") #Often warns about xlim when it is not an issue + ax[p].clear() + axis = ax[p] + else: + fig, axis = plt.subplots() + + if p == 'hvsr': + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) + elif p=='comp': + plotComponents[0] = plotComponents[0][:-1] + _plot_hvsr(hvsr_data, fig=fig, ax=axis, plot_type=plotComponents, xtype='x_freqs', show_legend=show_legend, axes=ax, **kwargs) + elif p=='spec': + plottypeKwargs = {} + for c in plotComponents: + plottypeKwargs[c] = True + kwargs.update(plottypeKwargs) + _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) + else: + warnings.warn('Plot type {p} not recognized', UserWarning) + + if show: + fig.canvas.draw() + + if return_fig: + return fig, ax + return + #Plot Obspy Trace in axis using matplotlib def plot_stream(stream, params, fig=None, axes=None, show_plot=False, ylim_std=0.75, return_fig=True): """Function to plot a stream of data with Z, E, N components using matplotlib. Similar to obspy.Stream.Plot(), but will be formatted differently and eventually more customizable. @@ -2687,6 +2887,27 @@

    Module sprit.sprit_hvsr

    """ orig_args = locals().copy() #Get the initial arguments + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'process_hvsr' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['process_hvsr'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(process_hvsr).args[1:], + inspect.getfullargspec(process_hvsr).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + method = orig_args['method'] + smooth = orig_args['smooth'] + freq_smooth = orig_args['freq_smooth'] + f_smooth_width = orig_args['f_smooth_width'] + resample = orig_args['resample'] + outlier_curve_std = orig_args['outlier_curve_std'] + verbose = orig_args['verbose'] + + + if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: pass @@ -2999,7 +3220,30 @@

    Module sprit.sprit_hvsr

    output : dict Dictionary similar to hvsr_data, but containing modified data with 'noise' removed """ - orig_args = locals().copy() #Get the initial arguments + #Get intput paramaters + orig_args = locals().copy() + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'remove_noise' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['remove_noise'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(remove_noise).args[1:], + inspect.getfullargspec(remove_noise).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + remove_method = orig_args['remove_method'] + sat_percent = orig_args['sat_percent'] + noise_percent = orig_args['noise_percent'] + sta = orig_args['sta'] + lta = orig_args['lta'] + stalta_thresh = orig_args['stalta_thresh'] + warmup_time = orig_args['warmup_time'] + cooldown_time = orig_args['cooldown_time'] + min_win_size = orig_args['min_win_size'] + remove_raw_noise = orig_args['remove_raw_noise'] + verbose = orig_args['verbose'] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: @@ -3121,6 +3365,13 @@

    Module sprit.sprit_hvsr

    else: output['stream'] = outStream['stream'] output['input_stream'] = hvsr_data['input_stream'] + + if 'processing_parameters' not in output.keys(): + output['processing_parameters'] = {} + output['processing_parameters']['remove_noise'] = {} + for key, value in orig_args.items(): + output['processing_parameters']['remove_noise'][key] = value + output['ProcessingStatus']['RemoveNoiseStatus'] = True output = _check_processing_status(output) @@ -3146,13 +3397,16 @@

    Module sprit.sprit_hvsr

    trEndTime = trace.stats.endtime outStream.merge() - output['stream'] = outStream + output['stream'] = outStream + elif isinstance(hvsr_data, obspy.core.stream.Stream) or isinstance(hvsr_data, obspy.core.trace.Trace): output = outStream else: warnings.warn(f"Output of type {type(output)} for this function will likely result in errors in other processing steps. Returning hvsr_data data.") return hvsr_data + + output = sprit_utils.make_it_classy(output) if 'xwindows_out' not in output.keys(): output['xwindows_out'] = [] @@ -3217,108 +3471,31 @@

    Module sprit.sprit_hvsr

    if t > win[0] and t < win[1]: psds_to_rid.append(i) elif nextT > win[0] and nextT < win[1]: - psds_to_rid.append(i) - - #Use dataframe - hvsrDF = params['hvsr_df'].copy() - psdVals = hvsrDF['psd_values_'+k] - hvsrDF[k+'_CurveMedian'] = psdVals.apply(np.nanmedian) - hvsrDF[k+'_CurveMean'] = psdVals.apply(np.nanmean) - - totMean = np.nanmean(hvsrDF[k+'_CurveMean']) - stds[k] = np.nanstd(hvsrDF[k+'_CurveMean']) - - hvsrDF['Use'] = hvsrDF['Use'].astype(bool) - #meanArr = hvsrDF[k+'_CurveMean'].loc[hvsrDF['Use']] - threshVal = totMean + outlier_std * stds[k] - hvsrDF['Use'] = hvsrDF[k+'_CurveMean'][hvsrDF['Use']].lt(threshVal) - hvsrDF['Use'] = hvsrDF['Use'].astype(bool) - - psds_to_rid = np.unique(psds_to_rid) - - for k in params['ppsds']: - for i, r in enumerate(psds_to_rid): - index = int(r-i) - params['ppsds'][k]['psd_values'] = np.delete(params['ppsds'][k]['psd_values'], index, axis=0) - params['ppsds'][k]['current_times_used'] = np.delete(params['ppsds'][k]['current_times_used'], index, axis=0) - return params - -###WORKING ON THIS -#Save default instrument and processing settings to json file(s) -def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False): - """Save settings to json file - - Parameters - ---------- - settings_path : str, default="default" - Where to save the json file(s) containing the settings, by default 'default'. - If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. - If 'all' is selected, a directory should be supplied. - Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory. - settings_type : str, {'all', 'instrument', 'processing'} - What kind of settings to save. - If 'all', saves all possible types in their respective json files. - If 'instrument', save the instrument settings to their respective file. - If 'processing', saves the processing settings to their respective file. By default 'all' - verbose : bool, default=True - Whether to print outputs and information to the terminal - - """ - fnameDict = {} - fnameDict['instrument'] = "instrument_settings.json" - fnameDict['processing'] = "processing_settings.json" - - if settings_path == 'default' or settings_path is True: - settingsPath = resource_dir.joinpath('settings') - else: - settings_path = pathlib.Path(settings_path) - if not settings_path.exists(): - if not settings_path.parent.exists(): - print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}') - settingsPath = pathlib.Path.home() - else: - settingsPath = settings_path.parent - - if settings_path.is_dir(): - settingsPath = settings_path - elif settings_path.is_file(): - settingsPath = settings_path.parent - fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json" - fnameDict['processing'] = settings_path.name+"_processingSettings.json" - - #Get final filepaths - instSetFPath = settingsPath.joinpath(fnameDict['instrument']) - procSetFPath = settingsPath.joinpath(fnameDict['processing']) + psds_to_rid.append(i) + + #Use dataframe + hvsrDF = params['hvsr_df'].copy() + psdVals = hvsrDF['psd_values_'+k] + hvsrDF[k+'_CurveMedian'] = psdVals.apply(np.nanmedian) + hvsrDF[k+'_CurveMean'] = psdVals.apply(np.nanmean) - #Get settings values - instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"] - procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report] + totMean = np.nanmean(hvsrDF[k+'_CurveMean']) + stds[k] = np.nanstd(hvsrDF[k+'_CurveMean']) + + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) + #meanArr = hvsrDF[k+'_CurveMean'].loc[hvsrDF['Use']] + threshVal = totMean + outlier_std * stds[k] + hvsrDF['Use'] = hvsrDF[k+'_CurveMean'][hvsrDF['Use']].lt(threshVal) + hvsrDF['Use'] = hvsrDF['Use'].astype(bool) - instrument_settings_dict = {} - processing_settings_dict = {} + psds_to_rid = np.unique(psds_to_rid) - for k in instKeys: - if isinstance(hvsr_data[k], pathlib.PurePath): - instrument_settings_dict[k] = hvsr_data[k].as_posix() - else: - instrument_settings_dict[k] = hvsr_data[k] - - for func in procFuncs: - funcName = func.__name__ - processing_settings_dict[funcName] = {} - for arg in inspect.getfullargspec(func)[0]: - if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)): - pass - else: - processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg] - - #Save settings files - if settings_type.lower()=='instrument' or settings_type.lower()=='all': - with open(instSetFPath.as_posix(), 'w') as instSetF: - json.dump(instrument_settings_dict, instSetF) - if settings_type.lower()=='processing' or settings_type.lower()=='all': - with open(procSetFPath.as_posix(), 'w') as procSetF: - json.dump(processing_settings_dict, procSetF) + for k in params['ppsds']: + for i, r in enumerate(psds_to_rid): + index = int(r-i) + params['ppsds'][k]['psd_values'] = np.delete(params['ppsds'][k]['psd_values'], index, axis=0) + params['ppsds'][k]['current_times_used'] = np.delete(params['ppsds'][k]['current_times_used'], index, axis=0) + return params #Read data as batch def batch_data_read(input_data, batch_type='table', param_col=None, batch_params=None, verbose=False, **readcsv_getMeta_fetch_kwargs): @@ -5721,7 +5898,6 @@

    Module sprit.sprit_hvsr

    if fig is None and ax is None: fig, ax = plt.subplots() - print(kwargs.keys()) if 'kwargs' in kwargs.keys(): kwargs = kwargs['kwargs'] @@ -5833,8 +6009,15 @@

    Module sprit.sprit_hvsr

    ax.axhline(hvsr_data['BestPeak']['f0'], c='k', linestyle='dotted', zorder=1000) if annotate: - xLocation = float(xmin) + (float(xmax)-float(xmin))*0.98 - ann = ax.text(x=xLocation, y=float(hvsr_data['BestPeak']['f0'])+0.3, fontsize='small', s=f"{hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va='bottom', + if float(hvsr_data['BestPeak']['f0']) < 1: + boxYPerc = 0.998 + vertAlign = 'top' + else: + boxYPerc = 0.002 + vertAlign = 'bottom' + xLocation = float(xmin) + (float(xmax)-float(xmin))*0.99 + yLocation = hvsr_data['input_params']['hvsr_band'][0] + (hvsr_data['input_params']['hvsr_band'][1]-hvsr_data['input_params']['hvsr_band'][0])*(boxYPerc) + ann = ax.text(x=xLocation, y=yLocation, fontsize='small', s=f"Peak at {hvsr_data['BestPeak']['f0']:0.2f} Hz", ha='right', va=vertAlign, bbox={'alpha':0.8, 'edgecolor':'w', 'fc':'w', 'pad':0.3}) ax.set_xlabel('UTC Time \n'+day) @@ -6871,6 +7054,21 @@

    Returns

    """ orig_args = locals().copy() #Get the initial arguments + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'check_peaks' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['check_peaks'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(check_peaks).args[1:], + inspect.getfullargspec(check_peaks).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + hvsr_band = orig_args['hvsr_band'] + peak_selection = orig_args['peak_selection'] + peak_freq_range = orig_args['peak_freq_range'] + verbose = orig_args['verbose'] + hvsr_data['processing_parameters']['check_peaks'] = {} for key, value in orig_args.items(): hvsr_data['processing_parameters']['check_peaks'][key] = value @@ -7091,6 +7289,139 @@

    Parameters

    return +
    +def export_settings(hvsr_data, export_settings_path='default', export_settings_type='all', include_location=False, verbose=False) +
    +
    +

    Save settings to json file

    +

    Parameters

    +
    +
    export_settings_path : str, default="default"
    +
    Where to save the json file(s) containing the settings, by default 'default'. +If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. +If 'all' is selected, a directory should be supplied. +Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    +
    export_settings_type : str, {'all', 'instrument', 'processing'}
    +
    What kind of settings to save. +If 'all', saves all possible types in their respective json files. +If 'instrument', save the instrument settings to their respective file. +If 'processing', saves the processing settings to their respective file. By default 'all'
    +
    include_location : bool, default=False, input CRS
    +
    Whether to include the location parametersin the exported settings document.This includes xcoord, ycoord, elevation, elev_unit, and input_crs
    +
    verbose : bool, default=True
    +
    Whether to print outputs and information to the terminal
    +
    +
    + +Expand source code + +
    def export_settings(hvsr_data, export_settings_path='default', export_settings_type='all', include_location=False, verbose=False):
    +    """Save settings to json file
    +
    +    Parameters
    +    ----------
    +    export_settings_path : str, default="default"
    +        Where to save the json file(s) containing the settings, by default 'default'. 
    +        If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to.
    +        If 'all' is selected, a directory should be supplied. 
    +        Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    +    export_settings_type : str, {'all', 'instrument', 'processing'}
    +        What kind of settings to save. 
    +        If 'all', saves all possible types in their respective json files.
    +        If 'instrument', save the instrument settings to their respective file.
    +        If 'processing', saves the processing settings to their respective file. By default 'all'
    +    include_location : bool, default=False, input CRS
    +        Whether to include the location parametersin the exported settings document.This includes xcoord, ycoord, elevation, elev_unit, and input_crs
    +    verbose : bool, default=True
    +        Whether to print outputs and information to the terminal
    +
    +    """
    +    fnameDict = {}
    +    fnameDict['instrument'] = "instrument_settings.json"
    +    fnameDict['processing'] = "processing_settings.json"
    +
    +    if export_settings_path == 'default' or export_settings_path is True:
    +        settingsPath = resource_dir.joinpath('settings')
    +    else:
    +        export_settings_path = pathlib.Path(export_settings_path)
    +        if not export_settings_path.exists():
    +            if not export_settings_path.parent.exists():
    +                print(f'The provided value for export_settings_path ({export_settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}')
    +                settingsPath = pathlib.Path.home()
    +            else:
    +                settingsPath = export_settings_path.parent
    +        
    +        if export_settings_path.is_dir():
    +            settingsPath = export_settings_path
    +        elif export_settings_path.is_file():
    +            settingsPath = export_settings_path.parent
    +            fnameDict['instrument'] = export_settings_path.name+"_instrumentSettings.json"
    +            fnameDict['processing'] = export_settings_path.name+"_processingSettings.json"
    +
    +    #Get final filepaths        
    +    instSetFPath = settingsPath.joinpath(fnameDict['instrument'])
    +    procSetFPath = settingsPath.joinpath(fnameDict['processing'])
    +
    +    #Get settings values
    +    instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"]
    +    inst_location_keys = ['xcoord', 'ycoord', 'elevation', 'elev_unit', 'input_crs']
    +    procFuncs = [fetch_data, remove_noise, generate_ppsds, process_hvsr, check_peaks, get_report]
    +
    +    instrument_settings_dict = {}
    +    processing_settings_dict = {}
    +
    +    for k in instKeys:
    +        if isinstance(hvsr_data[k], pathlib.PurePath):
    +            #For those that are paths and cannot be serialized
    +            instrument_settings_dict[k] = hvsr_data[k].as_posix()
    +        else:
    +            instrument_settings_dict[k] = hvsr_data[k]
    +
    +    if include_location:
    +        for k in inst_location_keys:
    +            if isinstance(hvsr_data[k], pathlib.PurePath):
    +                #For those that are paths and cannot be serialized
    +                instrument_settings_dict[k] = hvsr_data[k].as_posix()
    +            else:
    +                instrument_settings_dict[k] = hvsr_data[k]
    +
    +    
    +    for func in procFuncs:
    +        funcName = func.__name__
    +        processing_settings_dict[funcName] = {}
    +        for arg in hvsr_data['processing_parameters'][funcName]:
    +            if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)):
    +                pass
    +            else:
    +                processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg]
    +    
    +    #Save settings files
    +    if export_settings_type.lower()=='instrument' or export_settings_type.lower()=='all':
    +        with open(instSetFPath.with_suffix('.inst').as_posix(), 'w') as instSetF:
    +            jsonString = json.dumps(instrument_settings_dict, indent=2)
    +            #Format output for readability
    +            jsonString = jsonString.replace('\n    ', ' ')
    +            jsonString = jsonString.replace('[ ', '[')
    +            jsonString = jsonString.replace('\n  ]', ']')
    +            #Export
    +            instSetF.write(jsonString)
    +    if export_settings_type.lower()=='processing' or export_settings_type.lower()=='all':
    +        with open(procSetFPath.with_suffix('.proc').as_posix(), 'w') as procSetF:
    +            jsonString = json.dumps(processing_settings_dict, indent=2)
    +            #Format output for readability
    +            jsonString = jsonString.replace('\n    ', ' ')
    +            jsonString = jsonString.replace('[ ', '[')
    +            jsonString = jsonString.replace('\n  ]', ']')
    +            jsonString = jsonString.replace('\n  },','\n\t\t},\n')
    +            jsonString = jsonString.replace('{ "', '\n\t\t{\n\t\t"')
    +            jsonString = jsonString.replace(', "', ',\n\t\t"')
    +            jsonString = jsonString.replace('\n  }', '\n\t\t}')
    +            jsonString = jsonString.replace(': {', ':\n\t\t\t{')
    +               
    +            #Export
    +            procSetF.write(jsonString)
    +
    +
    def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detrend='spline', detrend_order=2, update_metadata=True, plot_input_stream=False, verbose=False, **kwargs)
    @@ -7101,7 +7432,30 @@

    Parameters

    Expand source code
    def fetch_data(params, source='file', trim_dir=None, export_format='mseed', detrend='spline', detrend_order=2, update_metadata=True, plot_input_stream=False, verbose=False, **kwargs):
    -    import warnings
    +    #Get intput paramaters
    +    orig_args = locals().copy()
    +    
    +    # Update with processing parameters specified previously in input_params, if applicable
    +    if 'processing_parameters' in params.keys():
    +        if 'fetch_data' in params['processing_parameters'].keys():
    +            defaultVDict = dict(zip(inspect.getfullargspec(fetch_data).args[1:], 
    +                        inspect.getfullargspec(fetch_data).defaults))
    +            defaultVDict['kwargs'] = kwargs
    +            for k, v in params['processing_parameters']['fetch_data'].items():
    +                # Manual input to function overrides the imported parameter values
    +                if k in orig_args.keys() and orig_args[k]==defaultVDict[k]:
    +                    orig_args[k] = v
    +
    +    #Update local variables, in case of previously-specified parameters
    +    source=orig_args['source']
    +    trim_dir=orig_args['trim_dir']
    +    export_format=orig_args['export_format']
    +    detrend=orig_args['detrend']
    +    detrend_order=orig_args['detrend_order']
    +    update_metadata=orig_args['update_metadata']
    +    plot_input_stream=orig_args['plot_input_stream']
    +    verbose=orig_args['verbose']
    +    kwargs=orig_args['kwargs']
     
         """Fetch ambient seismic data from a source to read into obspy stream
             
    @@ -7487,6 +7841,14 @@ 

    Parameters

    params['batch'] = False #Set False by default, will get corrected later in batch mode params['input_stream'] = dataIN.copy() params['stream'] = dataIN.copy() + + if 'processing_parameters' not in params.keys(): + params['processing_parameters'] = {} + params['processing_parameters']['fetch_data'] = {} + for key, value in orig_args.items(): + params['processing_parameters']['fetch_data'][key] = value + + params['ProcessingStatus']['FetchDataStatus'] = True if verbose and not isinstance(params, HVSRBatch): dataINStr = dataIN.__str__().split('\n') @@ -7586,7 +7948,24 @@

    Returns

    ppsd_kwargs = get_default_args(PPSD) ppsd_kwargs.update(ppsd_kwargs_sprit_defaults)#Update with sprit defaults, or user input - orig_args['ppsd_kwargs'] = [ppsd_kwargs] + orig_args['ppsd_kwargs'] = ppsd_kwargs + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'generate_ppsds' in hvsr_data['processing_parameters'].keys(): + defaultVDict = dict(zip(inspect.getfullargspec(generate_ppsds).args[1:], + inspect.getfullargspec(generate_ppsds).defaults)) + defaultVDict['ppsd_kwargs'] = ppsd_kwargs + for k, v in hvsr_data['processing_parameters']['generate_ppsds'].items(): + + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + remove_outliers = orig_args['remove_outliers'] + outlier_std = orig_args['outlier_std'] + verbose = orig_args['verbose'] + ppsd_kwargs = orig_args['ppsd_kwargs'] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: @@ -7781,7 +8160,8 @@

    Returns

    hvsr_data = sprit_utils.make_it_classy(hvsr_data) - hvsr_data['processing_parameters'] = {} + if 'processing_parameters' not in hvsr_data.keys(): + hvsr_data['processing_parameters'] = {} hvsr_data['processing_parameters']['generate_ppsds'] = {} for key, value in orig_args.items(): hvsr_data['processing_parameters']['generate_ppsds'][key] = value @@ -7990,11 +8370,26 @@

    Returns

    list/tuple - a list or tuple of the above objects, in the same order they are in the report_format list """ - #print statement - #Check if results are good - #Curve pass? orig_args = locals().copy() #Get the initial arguments + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_results.keys(): + if 'get_report' in hvsr_results['processing_parameters'].keys(): + for k, v in hvsr_results['processing_parameters']['get_report'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(get_report).args[1:], + inspect.getfullargspec(get_report).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + report_format = orig_args['report_format'] + plot_type = orig_args['plot_type'] + export_path = orig_args['export_path'] + return_results = orig_args['return_results'] + csv_overwrite_opt = orig_args['csv_overwrite_opt'] + no_output = orig_args['no_output'] + verbose = orig_args['verbose'] + hvsr_results['processing_parameters']['get_report'] = {} for key, value in orig_args.items(): hvsr_results['processing_parameters']['get_report'][key] = value @@ -8413,8 +8808,39 @@

    Returns

    return dataIN
    +
    +def import_settings(settings_import_path, settings_import_type='instrument', verbose=False) +
    +
    +
    +
    + +Expand source code + +
    def import_settings(settings_import_path, settings_import_type='instrument', verbose=False):
    +
    +    allList = ['all', ':', 'both', 'any']
    +    if settings_import_type.lower() not in allList:
    +        # if just a single settings dict is desired
    +        with open(settings_import_path, 'r') as f:
    +            settingsDict = json.load(f)
    +    else:
    +        # Either a directory or list
    +        if isinstance(settings_import_path, (list, tuple)):
    +            for setPath in settings_import_path:
    +                pass
    +        else:
    +            settings_import_path = sprit_utils.checkifpath(settings_import_path)
    +            if not settings_import_path.is_dir():
    +                raise RuntimeError(f'settings_import_type={settings_import_type}, but settings_import_path is not list/tuple or filepath to directory')
    +            else:
    +                instFile = settings_import_path.glob('*.inst')
    +                procFile = settings_import_path.glob('*.proc')
    +    return settingsDict
    +
    +
    -def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-28', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath='', hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], instrument_settings=None, verbose=False) +def input_params(datapath, site='HVSR Site', network='AM', station='RAC84', loc='00', channels=['EHZ', 'EHN', 'EHE'], acq_date='2023-10-30', starttime='00:00:00.00', endtime='23:59:59.999999', tzone='UTC', xcoord=-88.2290526, ycoord=40.1012122, elevation=755, input_crs='EPSG:4326', output_crs='EPSG:4326', elev_unit='feet', depth=0, instrument='Raspberry Shake', metapath=None, hvsr_band=[0.4, 40], peak_freq_range=[0.4, 40], processing_parameters={}, verbose=False)

    Function for designating input parameters for reading in and processing data

    @@ -8458,16 +8884,19 @@

    Parameters

    Depth of seismometer. Not currently used, but will likely be used in the future.
    instrument : str or list {'Raspberry Shake')
    Instrument from which the data was acquired.
    -
    metapath : str or pathlib.Path object, default=''
    -
    Filepath of metadata, in format supported by obspy.read_inventory. If default value of '', will read from resources folder of repository (only supported for Raspberry Shake).
    +
    metapath : str or pathlib.Path object, default=None
    +
    Filepath of metadata, in format supported by obspy.read_inventory. If default value of None, will read from resources folder of repository (only supported for Raspberry Shake).
    hvsr_band : list, default=[0.4, 40]
    Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later.
    peak_freq_range : list or tuple, default=[0.4, 40]
    Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range.
    -
    instrument_settings : None, str, default=None
    -
    The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. -If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). -The default settings can be reset using the save_settings() function with the parameter settings_path='default'.
    +
    processing_parameters={} : dict or filepath, default={}
    +
    If filepath, should point to a .proc json file with processing parameters (i.e, an output from sprit.export_settings()).
    +
    Note that this only applies to parameters for the functions: 'fetch_data', 'remove_noise', 'generate_ppsds', 'process_hvsr', 'check_peaks', and 'get_report.'
    +
    If dictionary, dictionary containing nested dictionaries of function names as they key, and the parameter names/values as key/value pairs for each key.
    +
    If a function name is not present, or if a parameter name is not present, default values will be used.
    +
    For example:
    +
    { 'fetch_data' : {'source':'batch', 'trim_dir':"/path/to/trimmed/data", 'export_format':'mseed', 'detrend':'spline', 'plot_input_stream':True, 'verbose':False, kwargs:{'kwargskey':'kwargsvalue'}} }
    verbose : bool, default=False
    Whether to print output and results to terminal
    @@ -8498,10 +8927,10 @@

    Returns

    elev_unit = 'feet', depth = 0, instrument = 'Raspberry Shake', - metapath = '', + metapath = None, hvsr_band = [0.4, 40], peak_freq_range=[0.4, 40], - instrument_settings=None, + processing_parameters={}, verbose=False ): """Function for designating input parameters for reading in and processing data @@ -8546,16 +8975,19 @@

    Returns

    Depth of seismometer. Not currently used, but will likely be used in the future. instrument : str or list {'Raspberry Shake') Instrument from which the data was acquired. - metapath : str or pathlib.Path object, default='' - Filepath of metadata, in format supported by obspy.read_inventory. If default value of '', will read from resources folder of repository (only supported for Raspberry Shake). + metapath : str or pathlib.Path object, default=None + Filepath of metadata, in format supported by obspy.read_inventory. If default value of None, will read from resources folder of repository (only supported for Raspberry Shake). hvsr_band : list, default=[0.4, 40] Two-element list containing low and high "corner" frequencies (in Hz) for processing. This can specified again later. peak_freq_range : list or tuple, default=[0.4, 40] Two-element list or tuple containing low and high frequencies (in Hz) that are used to check for HVSR Peaks. This can be a tigher range than hvsr_band, but if larger, it will still only use the hvsr_band range. - instrument_settings : None, str, default=None - The instrument_settings parameter is intended to enable rapid reading in of settings pertaining to the instrument you use most. - If set to "default" or True, will read in the default instrument settings (note, these are different than the default parameters of input_params(), even where names overlap). - The default settings can be reset using the save_settings() function with the parameter settings_path='default'. + processing_parameters={} : dict or filepath, default={} + If filepath, should point to a .proc json file with processing parameters (i.e, an output from sprit.export_settings()). + Note that this only applies to parameters for the functions: 'fetch_data', 'remove_noise', 'generate_ppsds', 'process_hvsr', 'check_peaks', and 'get_report.' + If dictionary, dictionary containing nested dictionaries of function names as they key, and the parameter names/values as key/value pairs for each key. + If a function name is not present, or if a parameter name is not present, default values will be used. + For example: + `{ 'fetch_data' : {'source':'batch', 'trim_dir':"/path/to/trimmed/data", 'export_format':'mseed', 'detrend':'spline', 'plot_input_stream':True, 'verbose':False, kwargs:{'kwargskey':'kwargsvalue'}} }` verbose : bool, default=False Whether to print output and results to terminal @@ -8567,30 +8999,6 @@

    Returns

    """ orig_args = locals().copy() #Get the initial arguments - #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment - global obspy - import obspy - if verbose: - print('Gathering input parameters (input_params())') - for key, value in orig_args.items(): - print('\t {}={}'.format(key, value)) - print() - - #Make Sure metapath is all good - if not pathlib.Path(metapath).exists() or metapath=='': - if metapath == '': - pass - else: - print('Specified metadata file cannot be read!') - repoDir = pathlib.Path(os.path.dirname(__file__)) - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')) - #print('Using default metadata file for Raspberry Shake v.7 distributed with package') - else: - if isinstance(metapath, pathlib.PurePath): - metapath = metapath.as_posix() - else: - metapath = pathlib.Path(metapath).as_posix() - #Reformat times if type(acq_date) is datetime.datetime: date = str(acq_date.date()) @@ -8673,12 +9081,6 @@

    Returns

    acq_date = datetime.date(year=int(date.split('-')[0]), month=int(date.split('-')[1]), day=int(date.split('-')[2])) raspShakeInstNameList = ['raspberry shake', 'shake', 'raspberry', 'rs', 'rs3d', 'rasp. shake', 'raspshake'] - #Raspberry shake stationxml is in the resources folder, double check we have right path - if instrument.lower() in raspShakeInstNameList: - if metapath == r'resources/rs3dv7_metadata.inv': - metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv7_metadata.inv')) - #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') - if output_crs is None: output_crs='EPSG:4326' @@ -8700,22 +9102,41 @@

    Returns

    } #Replace any default parameter settings with those from json file of interest, potentially - if instrument_settings is None: - instrument_settings_dict = {} - elif instrument_settings == "default" or instrument_settings is True: - # Update inputParamDict with default file - default_settings_json = settings_dir.joinpath('instrument_settings.json') - with open(default_settings_json.as_posix(), 'r') as f: - instrument_settings_dict = json.load(f) - else: - # Update inputParamDict with file - with open(instrument_settings, 'r') as f: - instrument_settings_dict = json.load(f) + instrument_settings_dict = {} + if pathlib.Path(instrument).exists(): + instrument_settings = import_settings(settings_import_path=instrument, settings_import_type='instrument', verbose=verbose) + input_params_args = inspect.getfullargspec(input_params).args + input_params_args.append('net') + input_params_args.append('sta') + for k, settings_value in instrument_settings.items(): + if k in input_params_args: + instrument_settings_dict[k] = settings_value + inputParamDict['instrument_settings'] = inputParamDict['instrument'] + inputParamDict.update(instrument_settings_dict) + + if instrument.lower() in raspShakeInstNameList: + if metapath is None: + metapath = pathlib.Path(pkg_resources.resource_filename(__name__, 'resources/rs3dv5plus_metadata.inv')).as_posix() + inputParamDict['metapath'] = metapath + #metapath = pathlib.Path(os.path.realpath(__file__)).parent.joinpath('/resources/rs3dv7_metadata.inv') for settingName in instrument_settings_dict.keys(): if settingName in inputParamDict.keys(): inputParamDict[settingName] = instrument_settings_dict[settingName] + #Declare obspy here instead of at top of file for (for example) colab, where obspy first needs to be installed on environment + if verbose: + print('Gathering input parameters (input_params())') + for key, value in inputParamDict.items(): + print('\t {}={}'.format(key, value)) + print() + + if isinstance(processing_parameters, dict): + inputParamDict['processing_parameters'] = processing_parameters + else: + processing_parameters = sprit_utils.checkifpath(processing_parameters) + inputParamDict['processing_parameters'] = import_settings(processing_parameters, settings_import_type='processing', verbose=verbose) + #Format everything nicely params = sprit_utils.make_it_classy(inputParamDict) params['ProcessingStatus']['InputParams'] = True @@ -8933,7 +9354,6 @@

    Returns

    elif p=='spec': plottypeKwargs = {} for c in plotComponents: - print(c) plottypeKwargs[c] = True kwargs.update(plottypeKwargs) _plot_specgram_hvsr(hvsr_data, fig=fig, ax=axis, colorbar=False, **kwargs) @@ -9222,6 +9642,27 @@

    Returns

    """ orig_args = locals().copy() #Get the initial arguments + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'process_hvsr' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['process_hvsr'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(process_hvsr).args[1:], + inspect.getfullargspec(process_hvsr).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + method = orig_args['method'] + smooth = orig_args['smooth'] + freq_smooth = orig_args['freq_smooth'] + f_smooth_width = orig_args['f_smooth_width'] + resample = orig_args['resample'] + outlier_curve_std = orig_args['outlier_curve_std'] + verbose = orig_args['verbose'] + + + if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: pass @@ -9584,7 +10025,30 @@

    Returns

    output : dict Dictionary similar to hvsr_data, but containing modified data with 'noise' removed """ - orig_args = locals().copy() #Get the initial arguments + #Get intput paramaters + orig_args = locals().copy() + + # Update with processing parameters specified previously in input_params, if applicable + if 'processing_parameters' in hvsr_data.keys(): + if 'remove_noise' in hvsr_data['processing_parameters'].keys(): + for k, v in hvsr_data['processing_parameters']['remove_noise'].items(): + defaultVDict = dict(zip(inspect.getfullargspec(remove_noise).args[1:], + inspect.getfullargspec(remove_noise).defaults)) + # Manual input to function overrides the imported parameter values + if k in orig_args.keys() and orig_args[k]==defaultVDict[k]: + orig_args[k] = v + + remove_method = orig_args['remove_method'] + sat_percent = orig_args['sat_percent'] + noise_percent = orig_args['noise_percent'] + sta = orig_args['sta'] + lta = orig_args['lta'] + stalta_thresh = orig_args['stalta_thresh'] + warmup_time = orig_args['warmup_time'] + cooldown_time = orig_args['cooldown_time'] + min_win_size = orig_args['min_win_size'] + remove_raw_noise = orig_args['remove_raw_noise'] + verbose = orig_args['verbose'] if (verbose and isinstance(hvsr_data, HVSRBatch)) or (verbose and not hvsr_data['batch']): if isinstance(hvsr_data, HVSRData) and hvsr_data['batch']: @@ -9706,6 +10170,13 @@

    Returns

    else: output['stream'] = outStream['stream'] output['input_stream'] = hvsr_data['input_stream'] + + if 'processing_parameters' not in output.keys(): + output['processing_parameters'] = {} + output['processing_parameters']['remove_noise'] = {} + for key, value in orig_args.items(): + output['processing_parameters']['remove_noise'][key] = value + output['ProcessingStatus']['RemoveNoiseStatus'] = True output = _check_processing_status(output) @@ -9731,13 +10202,16 @@

    Returns

    trEndTime = trace.stats.endtime outStream.merge() - output['stream'] = outStream + output['stream'] = outStream + elif isinstance(hvsr_data, obspy.core.stream.Stream) or isinstance(hvsr_data, obspy.core.trace.Trace): output = outStream else: warnings.warn(f"Output of type {type(output)} for this function will likely result in errors in other processing steps. Returning hvsr_data data.") return hvsr_data + + output = sprit_utils.make_it_classy(output) if 'xwindows_out' not in output.keys(): output['xwindows_out'] = [] @@ -10100,106 +10574,6 @@

    Raises

    return hvsr_results -
    -def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False) -
    -
    -

    Save settings to json file

    -

    Parameters

    -
    -
    settings_path : str, default="default"
    -
    Where to save the json file(s) containing the settings, by default 'default'. -If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to. -If 'all' is selected, a directory should be supplied. -Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    -
    settings_type : str, {'all', 'instrument', 'processing'}
    -
    What kind of settings to save. -If 'all', saves all possible types in their respective json files. -If 'instrument', save the instrument settings to their respective file. -If 'processing', saves the processing settings to their respective file. By default 'all'
    -
    verbose : bool, default=True
    -
    Whether to print outputs and information to the terminal
    -
    -
    - -Expand source code - -
    def save_settings(hvsr_data, settings_path='default', settings_type='all', verbose=False):
    -    """Save settings to json file
    -
    -    Parameters
    -    ----------
    -    settings_path : str, default="default"
    -        Where to save the json file(s) containing the settings, by default 'default'. 
    -        If "default," will save to sprit package resources. Otherwise, set a filepath location you would like for it to be saved to.
    -        If 'all' is selected, a directory should be supplied. 
    -        Otherwise, it will save in the directory of the provided file, if it exists. Otherwise, defaults to the home directory.
    -    settings_type : str, {'all', 'instrument', 'processing'}
    -        What kind of settings to save. 
    -        If 'all', saves all possible types in their respective json files.
    -        If 'instrument', save the instrument settings to their respective file.
    -        If 'processing', saves the processing settings to their respective file. By default 'all'
    -    verbose : bool, default=True
    -        Whether to print outputs and information to the terminal
    -
    -    """
    -    fnameDict = {}
    -    fnameDict['instrument'] = "instrument_settings.json"
    -    fnameDict['processing'] = "processing_settings.json"
    -
    -    if settings_path == 'default' or settings_path is True:
    -        settingsPath = resource_dir.joinpath('settings')
    -    else:
    -        settings_path = pathlib.Path(settings_path)
    -        if not settings_path.exists():
    -            if not settings_path.parent.exists():
    -                print(f'The provided value for settings_path ({settings_path}) does not exist. Saving settings to the home directory: {pathlib.Path.home()}')
    -                settingsPath = pathlib.Path.home()
    -            else:
    -                settingsPath = settings_path.parent
    -        
    -        if settings_path.is_dir():
    -            settingsPath = settings_path
    -        elif settings_path.is_file():
    -            settingsPath = settings_path.parent
    -            fnameDict['instrument'] = settings_path.name+"_instrumentSettings.json"
    -            fnameDict['processing'] = settings_path.name+"_processingSettings.json"
    -
    -    #Get final filepaths        
    -    instSetFPath = settingsPath.joinpath(fnameDict['instrument'])
    -    procSetFPath = settingsPath.joinpath(fnameDict['processing'])
    -
    -    #Get settings values
    -    instKeys = ["instrument", "net", "sta", "loc", "cha", "depth", "metapath", "hvsr_band"]
    -    procFuncs = [generate_ppsds, process_hvsr, check_peaks, get_report]
    -
    -    instrument_settings_dict = {}
    -    processing_settings_dict = {}
    -
    -    for k in instKeys:
    -        if isinstance(hvsr_data[k], pathlib.PurePath):
    -            instrument_settings_dict[k] = hvsr_data[k].as_posix()
    -        else:
    -            instrument_settings_dict[k] = hvsr_data[k]
    -    
    -    for func in procFuncs:
    -        funcName = func.__name__
    -        processing_settings_dict[funcName] = {}
    -        for arg in inspect.getfullargspec(func)[0]:
    -            if isinstance(hvsr_data['processing_parameters'][funcName][arg], (HVSRBatch, HVSRData)):
    -                pass
    -            else:
    -                processing_settings_dict[funcName][arg] = hvsr_data['processing_parameters'][funcName][arg]
    -    
    -    #Save settings files
    -    if settings_type.lower()=='instrument' or settings_type.lower()=='all':
    -        with open(instSetFPath.as_posix(), 'w') as instSetF:
    -            json.dump(instrument_settings_dict, instSetF)
    -    if settings_type.lower()=='processing' or settings_type.lower()=='all':
    -        with open(procSetFPath.as_posix(), 'w') as procSetF:
    -            json.dump(processing_settings_dict, procSetF)        
    -
    -
    def test_function()
    @@ -11130,12 +11504,14 @@

    Index

  • check_instance
  • check_peaks
  • export_data
  • +
  • export_settings
  • fetch_data
  • generate_ppsds
  • get_metadata
  • get_report
  • gui
  • import_data
  • +
  • import_settings
  • input_params
  • plot_hvsr
  • plot_stream
  • @@ -11143,7 +11519,6 @@

    Index

  • remove_noise
  • remove_outlier_curves
  • run
  • -
  • save_settings
  • test_function
  • diff --git a/pyproject.toml b/pyproject.toml index 8fb79f3..7d62bff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "sprit" authors = [{name="Riley Balikian"}, {name="Hongyu Xaio"}] dynamic = ["readme"] license = {file = "LICENSE"} -version="0.1.51" +version="0.1.50" description = "A package for processing and analyzing HVSR (Horizontal to Vertical Spectral Ratio) data" keywords = ["HVSR", "seismic", "horizontal to vertical spectral ratio", "obspy", 'geology', 'geophysics', 'geotechnical'] requires-python = ">=3.9" diff --git a/setup.py b/setup.py index a2fe0ed..3b9f9de 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ name="sprit", author= "Riley Balikian", author_email = "balikian@illinois.edu", - version="0.1.51", + version="0.1.50", package_data={'sprit': ['resources/*', 'resources/icon/*', 'resources/themes/*', 'resources/themes/forest-dark/*', 'resources/themes/forest-light/*', 'resources/sample_data/*','resources/settings/*']}, long_description_content_type="text/markdown", diff --git a/sprit/__pycache__/__init__.cpython-311.pyc b/sprit/__pycache__/__init__.cpython-311.pyc index 8ee45c406596307222f0881062cd63b83ecbbaec..94e86121f75cc8b6f8321514d6e06018f63b6c78 100644 GIT binary patch delta 622 zcmX@iJC%=jIWI340}%W;W}o_YBCjOlv5D%%0=eQ*;<*x05{wKfj43QRj1yO>$mJ?U zDdj3hDTBpWbC`2gqEvuvwkXvU_9(T9PpX}SZZQ{?=H22>ttiMZDv3`?EJ?h@59Ji6 zmXu`Xr5E4g&df!Ug>qoBo9h`P8I{#;aTOO7WtPNelob~hF#}y!Bn~2^L4-Vr5S#p) zNtT-v#00s%NM^D+iz)9dZiu$hlFXdq$uTTO^6zo_H~_ce7M*=>i2Afw=h4WL{REdSOPU4-5lGcv8!Dt;vdPxsw&xL?-vK$&0CR V@inl22w-I3lWX7t!6I#-mjNK>ruF~; delta 500 zcmbQrcbJ!VIWI340}$lI8>Ti*GAVL;Ih)lL%k!6&c9LQqIdy5;QxU?iQr+9K3i@DM* zPKZc)X=afePv^%B2ldzzD>}>n3}%`qT?DGJRkeIKq@1+tnsdWb>aqfz4?012%av6)wI8 T_76Uc41978Tp(Da3G^NSoWFxQ diff --git a/sprit/__pycache__/sprit_gui.cpython-311.pyc b/sprit/__pycache__/sprit_gui.cpython-311.pyc index 888dee2e21109ff631da5e4da79a7fdf0eb85336..83ae43677908f86f8018c479f8badf94354a1b7e 100644 GIT binary patch delta 19240 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 diff --git a/sprit/__pycache__/sprit_hvsr.cpython-311.pyc b/sprit/__pycache__/sprit_hvsr.cpython-311.pyc index ae0ffa1c00d9c15340a0ee3cb6655e054c9bbf5f..e332588d945512a0e45f1f978c2379f4213759ff 100644 GIT binary patch delta 101326 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 BBmDpX diff --git a/sprit/__pycache__/sprit_utils.cpython-311.pyc b/sprit/__pycache__/sprit_utils.cpython-311.pyc index eabf970794731c1e6f7b4b7d5050f6795df5760a..e3e5ecb22f712f5fdc595ba20c054c401796211c 100644 GIT binary patch delta 1075 zcmZ{iT}TvB6vywK*;#dScO4kZ9MmP#bfYy|-7HZ#XU8E}~r@*$8S*q6ZGx;HmVsLSv>cg{WMf6loxcP_w> z7olt1>2v_H=H9hNezd-D4Z)IwaBSfW-}>sG{M^%#9`OL}?Uc?ZDlHJJ~wNnsmO;%=b;?`%(${#j(h z`3E{MI7PsZ;i_UzhbX8y7sdcGo6Mt=FdB~s^Gd0is3^tPnq2rtp{oJ{o$Cg*wl1qM zstg{}dI!TOJV0)J-s0JYGFX7^fr=4}i;~ZvqRm7b@f7PRju6z^EysMe9M-~!&W^Sc zSa8T<`=g3g7(1p>@xCKrRaNce$n$HVVjATd2=_;Nqq*ag=pJ!4lK)HHN-zqh^Z11K zI*tc~5hu4kBa}}GD<_4OX`$K_s{N@V(#4`G1y!FrbnO^B_x9Qz7=1BUl zy632B&7QNv@nI@@$Q0|Q#QI6GJ}m}JF^~l;PrIN@v`q`k$0{#|E`<`I|LX5Hy)Ca) z)7zHQpG(~g+~qiQo6L?onm<4uR|eNWH{KBpz?V24l;LaqEm#8IV0%jsXOghaRBy{U zmIQv?)WS^@$K{l7^KQt^knJset_`goCp&bX;sM1&iboWWDV|U~CFtov&v1=YPO5cC kCyMCxBU%7aQ8a_!OQmo&^+nna1s;}U!D7_luJ&C&0nPy^NB{r; delta 654 zcmY+9&ubGw6vt;~v&|2i#FP|kZQ@`Lngpv6NrJR#(f+WPDl`_wLySAyc1f}uXD6+d z#Ci}x)I*p*!3rp54O9cxt2F`W!Vl%`suhRK@9vYQ?DlFjrYpF#I~rp5XjpyhBkJdtfjbjJ~G zIHDhYC-^UP#B+4p72k2i8?JagrF`{3e|^CB4Tg-avOhuK1C_t-3L@s^HN214I{SD#5$=!|qA4CrhM<#ACih>Ue$haa5(NQa+hdre zGRu-2%h5q0H9M^`lZsZz*|x0+31dvVWyx2}<@K!bx3)wd3NZx%uo>X3WLQzuWc_Vw zT~(TJfiI`uLqESbd>HQLw}%t(Ie#@wdSA#nyKJqL)vRJMXOgC>%^L+mU-I-wMs0}l z$@9X$`#mPl9mn=u-DBr^I~VohRQ;)%qD#8Zh)flP)z Date: Mon, 30 Oct 2023 16:47:55 -0500 Subject: [PATCH 17/17] add methods for data objects for export_settings --- sprit/__pycache__/sprit_hvsr.cpython-311.pyc | Bin 362863 -> 363251 bytes sprit/sprit_hvsr.py | 126 +++++++++++++++---- 2 files changed, 102 insertions(+), 24 deletions(-) diff --git a/sprit/__pycache__/sprit_hvsr.cpython-311.pyc b/sprit/__pycache__/sprit_hvsr.cpython-311.pyc index e332588d945512a0e45f1f978c2379f4213759ff..441745119ab8a33e9c285effa09e67cab0e17920 100644 GIT binary patch delta 1303 zcmb7?e@s(X6vyAW_rA8ktA(~ep%qFCIApLnb^P)dgBc*e*qTgmzd~%&ZH^yn%XA78 zGMCJeImR0nfBZo(4h&5$!<{+lbIzux%Zsp zzVG*a&O7(r=#0D76}RZHS{*6yIF+)_p}Kl(QTsVk9qHSDE?!hJm&T(O1ns^Xo3HV9!+cG!C(4Th1>W+n zX_lgyxhx*F*LVUf=J2~0qFJ|JI0S-lgztL+0!J>9djpS0-N$x-t5Ti}q{&%qfm=BE z-(230ToUY-TL%p^-M&P=7`)eqp7)XdAm9E-v&!3spwE8r^kH4ujW%InXJq(_h7=0@ zHto|}8yj4n*Xo^So5K^XZn-p+mtrohcY9n-b&h(E*?s?k*)gwm*v*~>v)mRcvurb0 zxf(V(-0radp)%k3P!u7n6~5uiNz^C_D$TS2V(O?$?O$XGsH{UOt6yckqBP3t@eJ8| z<&R~OZdxD_)=??OuPr+IB2Rn;q~al|*e@0HomSLsQrykz~a?^xi zhe`N~ zgKL22kXYw-IP4*XyV3XaMCVGlfv*~1X#}LBoC3bJgebfvwcsOy0`JxWBd`&P9gGxC zc$1uP2Kw--6Fy=PM9&-0k_y-HW(OoLg)vn3!JGw5s2YPmbx@4AXW)1ge2+)&!f%x8 z+ku?Um z5bf5$EV`y>PzmL@{wCe20uP>_qj%JB3V%?svr5q52hnVnf(2XkY>vPaxILb|pn%nQ zX)*g7_@w%i*(W;KfwjxoqycPLTF$BnWa7R`w%5SFYlV~bMiVg>C$=!Fnj01GvB3y< z3rBXdTS_>97xu7rfB~%O4jV`C%YAHCgx|5Jhiyp&9UA&riv*W(&yTE34}anKuPi^4 z15HY?I|dr?TZ1^P=7ZjyE`FthDO{B=PKK{KwNkVu@Bx=N#oQRsV%t`+Bht_a!2i#G MWjkiPE3Q}k0~h+N9RL6T delta 985 zcmaJ;eN5X`6zzHU{pbfRwPlpj76`%^g}`vi0yc_nak>cs)?`taNgM^cAeu0Bj%3Ce zAk9o-zN{!#i9ZFN>BLU45(s!ihBIT*r z&>H0$El&K0u>;Kos%CVxx*`6h>>2%k$~d(zl)fU_7VA`7F6F1KNObEidsdvr-NF#^ z1P$u9uUwXR6F>gk=;Eb>1m&10Q%_B7j*d+v3el%UXD2gACK*gVRXgoho36|YlP_93 zT?ci3CS83nV_8Rr^^an>)@jdNJK9Vz_k6n;*|J>0T@-b9??wab_y9d9S`MS+eVDH8T94DyvK z(d$Jszu71w`T`qcBVnbxyL$|OoGUlZ@Jj9>* zCsvo0 z%&y(k?ZgD1e}!tz*vjb#sWcTY z^R{jpaq21NeMH$71i0c$8nf#1Im1+H#rwSDd+M~~7yjc%iW%_$_b*VZ2~AvelNN0_ z%MV)Q1tUzHv?;o{2h8q)midzGG5>x?vitERPbP>tQN@O zjB0t*tv~B*Sf0F1+*-vAdu6swA3V?|N6mPKcXY@FBaU!Qr)&o<^5oI@;3yY$$?F=X zIOmw$