164 Commits
Author SHA1 Message Date
marcel 274bf31098 [minor] add submit_fmtomo.sh 2025-04-10 15:29:31 +02:00
marcel f48793e359 [update] add some lost files 2025-04-10 15:07:31 +02:00
marcel 80fd07f0f8 [update] add shell scripts 2025-04-10 14:54:49 +02:00
marcel cec6cc24c5 [initial commit] 2025-04-10 13:58:01 +02:00
marcel 8a1da72d1c [update] increased robustness of correlation picker. If autoPyLoT fails on the stacked trace it tries to pick other stacked traces. Ignoring failed autoPyLoT picks can manually be set if they are not important (e.g. when only pick differences are important) 2025-04-03 11:23:17 +02:00
marcel c989b2abc9 [update] re-implemented code that was lost when corr_pick was integrated in pylot. Use all reference-pick-corrected theoretical picks as correlation reference time instead of using only reference picks 2025-03-19 15:43:24 +01:00
marcel 2dc27013b2 Update README.md 2025-03-06 12:18:41 +01:00
marcel 4bd2e78259 [bugfix] explicitly pass parameters to "picksdict_from_picks" to calculate pick weights. Otherwise no weights could be calculated. Closes #40 2024-11-20 17:16:15 +01:00
marcel 468a7721c8 [bugfix] changed default behavior of PylotParameter class to use default Parameter if called without input parameters. Related to #40 2024-11-20 17:01:53 +01:00
marcel 555fb8a719 [minor] small code fixes 2024-11-20 16:57:27 +01:00
marcel 5a2a1fe990 [bugfix] flawed logic after parameter renaming corrected 2024-11-20 11:14:27 +01:00
marcel 64b719fd54 [minor] increased robustness of correlation algorithm for unknown exceptions... 2024-11-20 11:13:15 +01:00
marcel 71d4269a4f [bugfix] reverting code from commit 3069e7d5. Checking for coordinates in dataless Parser IS necessary to make sure correct Metadata were found. Fixes #37.
[minor] Commented out search for network name in metadata filename considered being unsafe
2024-10-09 17:07:22 +02:00
marcel 81e34875b9 [update] small changes increasing code robustness 2024-10-09 16:59:12 +02:00
marcel d7ee820de3 [minor] adding missing image to doc 2024-09-30 16:40:41 +02:00
marcel 621cbbfbda [minor] modify README 2024-09-18 16:59:34 +02:00
marcel 050b9fb0c4 Merge remote-tracking branch 'origin/develop' into develop 2024-09-18 16:57:42 +02:00
marcel eb3cd713c6 [update] add description for pick correlation algorithm 2024-09-18 16:56:54 +02:00
marcel 18c37dfdd0 [bugfix] take care of more unescaped backslashes in Metadata 2024-09-16 16:27:36 +02:00
marcel 9333ebf7f3 [update] deactivate Spectrogram tab features in main branch 2024-09-12 16:58:27 +02:00
marcel 8c46b1ed18 [update] README.md 2024-09-12 16:54:39 +02:00
marcel c743813446 Merge branch 'refs/heads/develop'
# Conflicts:
#	PyLoT.py
#	README.md
#	pylot/core/util/widgets.py
2024-09-12 16:32:15 +02:00
marcel 41c9183be3 Merge branch 'refs/heads/correlation_picker' into develop 2024-09-12 16:24:50 +02:00
marcel ae6c4966a9 [bugfix] compare options always activated using obspy_dmt independent of data availability 2024-09-12 12:23:18 +02:00
marcel e8a516d16b [update] trying to increase plot performance for large datasets, can need overhaul of drawPicks method in the future (too much recursion) 2024-09-12 12:19:44 +02:00
marcel f78315dec4 [update] new test files for test_autopicker after changes in autopicker 2024-09-11 11:02:32 +02:00
marcel 28f75cedcb Merge branch 'refs/heads/develop' into correlation_picker 2024-09-11 10:31:50 +02:00
marcel e02b62696d [bugfix] no actual UTCDateTime object was used to check metadata availability for check4rotated 2024-09-10 16:59:02 +02:00
marcel e4217f0e30 [critical] fixing a major bug in checksignallength, testing needed 2024-09-10 16:58:12 +02:00
marcel 8f154e70d7 [minor] plot coloring 2024-09-10 16:57:18 +02:00
marcel 6542b6cc4f [minor] slightly improved test output 2024-09-10 16:57:00 +02:00
marcel 5ab6c494c5 [update] increased code readability and improved figures created in autopick.py and picker.py 2024-09-10 16:16:46 +02:00
marcel 3da47c6f6b [revert] changed slope calculation in AICPicker back to older state (probably causing problems changing results in test_autopickstation.py) 2024-09-09 16:56:38 +02:00
marcel cc7716a2b7 [minor] improved unittest result 2024-09-09 16:05:02 +02:00
marcel 03947d2363 [update] removed bad STA/LTA implementation from CF class 2024-09-09 14:42:54 +02:00
marcel e1b0d48527 [refactor] removed unused parameter "data" from calcCF methods 2024-09-09 14:20:41 +02:00
marcel 431dbe8924 [testing] improved dictionary comparison. Failed tests have completely different picks (not only snrdb) 2024-08-30 15:07:31 +02:00
marcel 63810730e5 [bugfix] added missing parameter "taup_phases" introduced a long time ago into default parameters and parameters for unit tests 2024-08-30 14:51:30 +02:00
marcel f2159c47f9 [testing] brought test_autopickstation up-to-date using, removing deprecated methods and using pytest.approx
Certain tests fail on snrdb calculation which has to be examined (WIP)
2024-08-30 12:41:16 +02:00
marcel d0fbb91ffe [update] added test for AutoPyLoT, added test files for correlation picker as well 2024-08-29 16:46:30 +02:00
marcel 424d42aa1c Merge branch 'refs/heads/develop' into correlation_picker
# Conflicts:
#	pylot/core/pick/charfuns.py
#	tests/test_autopicker/pylot_alparray_mantle_corr_stack_0.03-0.5.in
#	tests/test_autopicker/test_autopylot.py
2024-08-29 16:37:15 +02:00
marcel 2cea10088d [update] added test for AutoPyLoT, added test files for correlation picker as well 2024-08-29 16:35:04 +02:00
marcel 5971508cab [bugfix] Metadata object did not find inventory for relative directory paths/unescaped backslashes 2024-08-29 16:34:37 +02:00
marcel c765e7c66b [bugfix] fixed import for tukey in newer scipy versions which moved to signal.windows module 2024-08-29 16:33:39 +02:00
marcel 466f19eb2e [bugfix] fixed import for tukey in newer scipy versions 2024-08-28 18:01:03 +02:00
marcel e6a4ba7ee2 [update] remove mean from picks (WIP for residual plotting) pt2 2024-08-28 10:37:31 +02:00
marcel 5d90904838 Merge branch 'develop' into correlation_picker 2024-08-27 17:46:21 +02:00
marcel 7a13288c85 [major] getting rid of unused/unnecessary "rootpath" and "database" structure. Testing required. 2024-08-27 17:45:15 +02:00
marcel 3f97097bf6 [update] add mailmap for better readability of git commit history 2024-08-27 16:19:19 +02:00
marcel 29107ee40c [update] WIP: adding tests for autopylot (global) 2024-08-26 17:18:41 +02:00
marcel fa310461d0 [update] added possibility to remove the mean from picks (WIP for residual plotting) 2024-08-15 17:15:10 +02:00
marcel 42a7d12292 [bugfix] reduce maximum number of stations listed in array map status 2024-08-15 16:30:05 +02:00
marcel 2e49813292 [update] some general bugfixes and improvements in array map 2024-08-14 17:04:17 +02:00
marcel 5d6f4619cc [update] add selection for merge strategy for loading of single event files 2024-08-12 16:03:29 +02:00
marcel db11e125c0 [todos] add todos 2024-08-09 16:53:21 +02:00
marcel b59232d77b [bugfix] function name accidentally overwritten on parameter renaming 2024-08-09 16:52:57 +02:00
marcel 176e93d833 [refactor] finished annotations (type hints) 2024-08-09 16:52:32 +02:00
marcel 759e7bb848 [bugfix] partially reverted signature of an inner function with shadowed variable name
[refactor] minor
2024-08-09 16:24:40 +02:00
marcel 61c3f40063 Merge branch 'develop' into correlation_picker 2024-08-09 15:50:46 +02:00
marcel 213819c702 [update] simplify dependencies (remove sub-dependencies), update installation instructions in README.md 2024-08-09 15:50:02 +02:00
marcel 67f34cc871 Merge branch 'develop' into correlation_picker
# Conflicts:
#	pylot.yml
#	requirements.txt
2024-08-09 15:05:30 +02:00
marcel f4f48a930f [refactor] moved unittest to existing test folder 2024-08-09 15:03:55 +02:00
marcel b41e2b2de6 [update] new requirements.txt and pylot.yml for python 3.11 2024-08-09 15:02:31 +02:00
marcel a068bb8457 [update] refactoring, added type hints 2024-08-08 16:49:15 +02:00
marcel 452f2a2e18 [bugfix] test raised different Exception than planned 2024-08-08 14:41:16 +02:00
marcel c3a2ef5022 [minor] changed test to be approximately equal to test result on different machine 2024-08-08 11:28:10 +02:00
marcel 8e7bd87711 [new] added some unit tests for correlation picker (WIP) 2024-08-07 17:11:27 +02:00
marcel d5817adc46 [merge] changes to correlation picker from different machines that were not committed 2024-08-07 10:17:35 +02:00
marcel 14f01ec46d Merge branch 'correlation_picker' of git.geophysik.ruhr-uni-bochum.de:marcel/pylot into correlation_picker 2024-08-07 10:08:57 +02:00
marcel 1b074d14ff [update] WIP: Adding type hints, docstrings etc. 2024-08-06 16:03:50 +02:00
marcel ce71c549ca [bugfix] removed parameter that was re-introduced accidentally from manual merge 2024-08-06 16:03:16 +02:00
marcel c4220b389e Merge branch 'correlation_picker' of git.geophysik.ruhr-uni-bochum.de:marcel/pylot into correlation_picker 2024-07-25 15:36:06 +02:00
marcel 0f29d0e20d [minor] small modifications (naming conventions) 2024-07-25 14:50:40 +02:00
marcel e1e0913e3a Merge remote-tracking branch 'origin/develop' into develop 2024-07-25 10:25:59 +02:00
marcel cdcd226c87 [initial] adding files from correlation picker 2024-07-24 14:07:13 +02:00
marcel 5f53cc5365 [bugfix] renamed method inside array_map.py 2024-07-23 16:31:52 +02:00
marcel 6cce05b035 Merge branch 'feature/dae' into develop
# Conflicts:
#	pylot/core/io/data.py
#	pylot/core/util/widgets.py
2024-06-12 16:19:21 +02:00
marcel 7326f061e5 [minor] inform if station coordinates were not found in metadata 2024-06-12 16:11:53 +02:00
marcel 1a18401fe3 [bugfix] added missing Parameter object in call for picksdict_from_picks 2024-06-12 13:44:02 +02:00
marcel ec930dbc12 [minor] removed unneeded imports 2024-06-07 15:56:05 +02:00
marcel b991f771af [bugfix] removing redundancy and wrong bullsh.. try-except code 2024-06-07 15:04:16 +02:00
marcel 2c3b1876ab [minor] switch default cmap for array_map to 'viridis' 2024-06-07 14:34:27 +02:00
marcel 0acd23d4d0 [update] further improved Pickfile selection dialog, now providing methods "overwrite" or "merge" 2024-06-07 14:32:57 +02:00
marcel f349c8bc7e [update] improve pickfile selection, give the ability to select only specific files 2024-06-07 13:09:34 +02:00
marcel 6688ef845d [bugfix] re-implement ability of get_bool to return unidentifiable input 2024-06-07 13:08:51 +02:00
marcel 5b18e9ab71 [merge] merge branch 'improve-util-utils' of pull request #35 into develop 2024-06-07 10:29:39 +02:00
marcel c79e886d77 [minor] update default shell script for SGE 2024-06-06 15:55:47 +02:00
marcel 76f2d5d972 [update] improve SearchForFileExtensionDialog now proposing available file endings 2024-06-06 15:54:36 +02:00
marcel 2d08fd029d [hotfix] datetime formatting caused error when time not set 2024-06-06 13:55:11 +02:00
marcel 8f22d438d3 [update] changed eventbox overview to show P and S onsets separately 2024-06-05 16:18:25 +02:00
marcel 93b7de3baa [update] raising PickingFailedException when CF cannot be calculated due to missing signal (too short waveform)
[update] raising PickingFailedException when CF cannot be calculated due to missing signal (too short waveform)
2024-06-05 14:31:09 +02:00
marcel 05642e775b [minor] some tweaks (convenience)
[update] raising PickingFailedException when CF cannot be calculated due to missing signal (too short waveform)
2024-06-05 14:31:07 +02:00
marcel 47205ca493 [update] improved calculation of smoothed AIC. Old code always created an artificial value and a np.nan at the array start 2024-06-05 14:31:07 +02:00
marcel 5c7f0b56eb [update] improved SearchFileByExtensionDialog widget 2024-06-05 14:19:17 +02:00
marcel c574031931 [bugfix] the implementation approach of STA/LTA inside characteristic function calculation (skewness/kurtosis) corrupted the old, working code due to a mistake in the logic 2024-06-05 14:17:57 +02:00
marcel e1a0fde619 [update] improved array map to identify and display other phase names than P / S 2024-05-29 11:43:31 +02:00
marcel 48d196df11 [update] improved SearchFileByExtensionDialog widget (table, auto refresh) 2024-05-29 11:42:10 +02:00
marcel 6cc9cb4a96 [minor] reduced maxtasksperchild for multiprocessing 2024-05-29 11:40:34 +02:00
marcel 5eab686445 [bugfix] accidentally removed return value 2024-05-24 16:10:32 +02:00
marcel b12e7937ac [update] added a widget for loading pick files that lists available files depending on chosen filemask 2024-05-22 10:58:07 +02:00
marcel 78f2dbcab2 [update] added check for nan values in waveforms which crashed obspy filter routines
[minor] some tweaks and optimisations
2024-04-30 15:48:54 +02:00
marcel 31ca0d7a85 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	pylot/core/util/widgets.py
2024-04-09 16:12:03 +02:00
marcel 8b95c7a0fe [update] changed sorting of traces overview if all station names are numeric (e.g. active experiments) 2024-04-09 16:02:31 +02:00
marcel c7f9ad4c6f [update] changed sorting of traces overview if all station names are numeric (e.g. active experiments) 2024-04-09 15:53:19 +02:00
marcel 65dbaad446 [update] adding possibility to display other waveform data (e.g. denoised/synthetic) together with genuine data for comparison 2024-03-22 17:12:04 +01:00
marcel 5b97d51517 [minor] mpl.figure.canvas.draw -> draw_idle 2024-03-22 17:12:04 +01:00
marcel f03ace75e7 [bugfix] QWidget.show() killed figure axis dimensions creating unexpected error of fig.aspect=0 when creating colorbar inset_axes in Python 3.11 2024-03-22 17:10:04 +01:00
marcel 9c78471d20 [bugfix] header resize method renamed in QT5 2024-03-22 15:34:05 +01:00
marcel 09d2fb1022 [bugfix] pt2 of fmpicker fix, make sure to also copy stream in autoPyLoT
closes #24
2023-08-24 12:55:30 +02:00
marcel 3cae6d3a78 [bugfix] use copies of wfdata when calling fmpicker to prevent modification of actual data used inside GUI 2023-08-24 11:28:30 +02:00
marcel 2e85d083a3 [bugfix] do not call calcsourcespec if incidence angle is outside bounds (for whatever reason) 2023-08-24 11:27:30 +02:00
marcel ba4e6cfe50 [bugfix] bin directory + /bin creates "/bin/bin". Also it is not taken care of os compatibility and also compatibility with existing code (line 86ff was useless after recent change in line 85) 2023-08-23 14:48:21 +02:00
marcel 1f16d01648 [minor] give more precise user warning if no pick channel was selected 2023-08-23 09:38:16 +02:00
marcel 3069e7d526 [minor] commented - possibly unnecessary - line of code that created an error when using old metadata Parser 2023-08-22 15:53:49 +02:00
marcel a9aeb7aaa3 [bugfix] set simple phase hint (P or S) 2023-08-22 15:53:49 +02:00
marcel b9adb182ad [bugfix] could not handle asterisk-marked events when opening tune-autopicker 2023-08-22 15:53:49 +02:00
marcel a823eb2440 [workaround] using explicit Exception definition without a special handling does not make sense. Function broke on other errors in polyfit. Still might need fixes in the two lines above the "except" block(s). 2023-08-22 15:53:49 +02:00
marcel 486e3dc9c3 Merge pull request 'Disabled button in case flag is false' (#31) from disable-show-log-widget into develop
Reviewed-on: #31
2023-08-22 12:05:33 +02:00
marcel 8d356050d7 [update] corrected original authors of PILOT 2023-08-22 12:01:51 +02:00
jeldrik 43cab3767f [Bugfix] fixxed wrong check for taupymodel 2023-06-27 08:04:00 +02:00
sebastianw b3fdbc811e Merge branch 'develop' into improve-util-utils 2023-06-23 09:37:54 +02:00
jeldrik a1f6c5ffca Bugfixxes, spectogram tab wip 2023-06-14 13:11:54 +02:00
jeldrik e4e7afa996 Minor changes to adjust to python 3. Temporary Fix for file exporting not working properly. WIP spectrogram view. 2023-04-27 10:24:55 +02:00
sebastianw 9fce4998d3 bugfix: remove unused functions; correct for wrong formatting (PEP) 2023-04-23 22:05:11 +02:00
sebastianw c468bfbe84 feat: add type hints and tests for plot utils 2023-04-23 21:37:20 +02:00
sebastianw 4861d33e9a bugfix: add tests to key_for_set_value 2023-04-16 09:58:51 +02:00
sebastianw f5f4635c3d bugfix: rename is_iterable and add doc tests 2023-04-16 09:50:42 +02:00
sebastianw b12d92eebb bugfix: refactor get_owner and get_hash; add tests 2023-04-12 21:22:58 +02:00
sebastianw e9da81376e bugfix: add new tests and refactor get_none 2023-04-12 20:32:44 +02:00
sebastianw e68fc849f0 bugfix: correct erroneous and add new doctests 2023-04-10 19:14:23 +02:00
sebastianw efb117177c bugfix: update check4rotate 2023-04-10 18:35:58 +02:00
sebastianw 0634d24814 fix: disabled button in case flag is false
The button was not disabled in case the flag variable was false. The get_Bool function was renamed and improved to also work in case in the input variable is of type int or float.

Additionally, the environment file was corrected to also work for macOS installations with ARM architecture.
2023-04-06 16:40:20 +02:00
jeldrik 43c2b97b3d Small changes 2023-01-24 11:45:12 +01:00
ann-christin 8d94440e77 [bugfix] logwidget always initiated 2022-11-14 14:14:59 +01:00
ann-christin 66b7dea706 [update] pylot.in no longer mandatory 2022-11-14 14:14:12 +01:00
ann-christin ebf6d4806a [minor] reformating 2022-11-14 11:52:25 +01:00
ann-christin 207d0b3a6f [update] directly pass args from arg parser 2022-11-14 11:18:15 +01:00
ann-christin 3b3bbc29d1 Merge remote-tracking branch 'origin/develop' into develop 2022-11-14 10:30:38 +01:00
jeldrik 0c3fca9299 Re-Added local changes that had been lost due to technical problems ( no access to old machine ) 2022-10-04 11:44:31 +02:00
jeldrik 2d33a60421 [Bugfix] Multiple small bugfixxes keeping NLL from working in python3.+ 2022-09-15 14:31:13 +02:00
ann-christin a8c6f4c972 [reformat] spell checking 2022-08-25 15:31:08 +02:00
ann-christin 0d91f9e3fe update github link 2022-08-25 14:03:05 +02:00
ann-christin 494d281d61 update github link 2022-08-25 14:00:37 +02:00
kaan 5ef427ec12 Merge branch 'develop' of git.geophysik.ruhr-uni-bochum.de:marcel/pylot into develop 2022-06-29 14:07:35 +02:00
kaan 29cf978782 minor bug fixes 2022-06-29 14:07:06 +02:00
marcel 091449819c [update] tau-p usage for s-picking 2022-05-31 18:16:02 +02:00
marcel cd9c139349 [bugfix] mainly added missing taup model (lost in branch merging for python3), some smaller fixes for S picking 2022-05-31 12:45:22 +02:00
marcel 084fb10cea Merge remote-tracking branch 'origin/develop' into develop 2022-05-31 09:59:17 +02:00
marcel dde9520879 [minor] deactivate logwidget by default as it seems to irregularly create segfaults 2022-05-31 09:40:15 +02:00
marcel 7847f40a35 [minor] added developers do README 2022-04-01 12:07:51 +02:00
ludger 2c188432a1 Merge branch 'develop' of https://git.geophysik.ruhr-uni-bochum.de/marcel/pylot into develop 2022-03-24 13:28:52 +01:00
marcel 86dc0f5436 [minor] small style changes 2022-03-22 11:26:29 +01:00
marcel 83ba63a3fd Merge pull request 'feature/port-to-py3' (#11) from feature/port-to-py3 into develop
Reviewed-on: #11
2022-03-21 15:30:05 +01:00
ludger 3392100206 Removed psd 2022-03-17 13:42:27 +01:00
marcel 401265eb3a [minor] added some comments 2022-03-16 16:03:54 +01:00
marcel dd685d5d5e [refactor] rewrote/simplified getQualitiesfromxml code, used function already implemented in phases.py 2022-03-16 16:00:14 +01:00
marcel 3cd17ff364 [update] added xml file for unittest 2022-03-16 14:31:03 +01:00
marcel d879aa1a8b [bugfix] small fix to get getQualitiesfromxml running, added unittest for function 2022-03-16 14:29:47 +01:00
marcel 445f1da5ac [bugfix] reset stdout to previously set one and not to default sys.__stdout__ 2022-03-16 09:25:28 +01:00
marcel 710ea57503 Merge branch 'github-master' 2017-09-25 15:50:38 +02:00
marcel 8aaad643ec release version 0.2
release notes:
==============
Features:
- centralize all functionalities of PyLoT and control them from within the main GUI
- handling multiple events inside GUI with project files (save and load work progress)
- GUI based adjustments of pick parameters and I/O
- interactive tuning of parameters from within the GUI
- call automatic picking algorithm from within the GUI
- comparison of automatic with manual picks for multiple events using clear differentiation of manual picks into 'tune' and 'test-set' (beta)
- manual picking of different (user defined) phase types
- phase onset estimation with ObsPy TauPy

- interactive zoom/scale functionalities in all plots (mousewheel, pan, pan-zoom)
- array map to visualize stations and control onsets (beta feature, switch to manual picks not implemented)

Platform support:
- python 3 support
- Windows support

Performance:
- multiprocessing for automatic picking and restitution of multiple stations
- use pyqtgraph library for better performance on main waveform plot

Visualization:
- pick uncertainty (quality classes) visualization with gradients
- pick color unification for all plots
- new icons and stylesheets

Known Issues:
2017-09-25 14:24:52 +02:00
marcel bc808b66c2 [update] README.md 2017-09-25 10:17:58 +02:00
marcel 472e5b3b9e Merge branch 'develop' 2017-09-21 16:18:53 +02:00
Marc S. Boxberg 503ea419c4 release version: 0.1a
release notes:
==============
Features
- consistent manual phase picking through predefined SNR dependant zoom level
- uniform uncertainty estimation from waveform's properties for automatic and manual picks
- pdf representation and comparison of picks taking the uncertainty intrinsically into account
- Richter and moment magnitude estimation
- location determination with external installation of [NonLinLoc](http://alomax.free.fr/nlloc/index.html)
Known issues
- Magnitude estimation from manual PyLoT takes some time (instrument correction)
2016-10-04 09:38:05 +02:00
94 changed files with 189406 additions and 110456 deletions
+1
View File
@@ -2,3 +2,4 @@
*~
.idea
pylot/RELEASE-VERSION
/tests/test_autopicker/dmt_database_test/
+39
View File
@@ -0,0 +1,39 @@
Darius Arnold <Darius.Arnold@ruhr-uni-bochum.de> <Darius_A@web.de>
Darius Arnold <Darius.Arnold@ruhr-uni-bochum.de> <darius.arnold@rub.de>
Darius Arnold <Darius.Arnold@ruhr-uni-bochum.de> <darius.arnold@ruhr-uni-bochum.de>
Darius Arnold <Darius.Arnold@ruhr-uni-bochum.de> <mail@dariusarnold.de>
Dennis Wlecklik <dennisw@minos02.geophysik.ruhr-uni-bochum.de>
Jeldrik Gaal <jeldrikgaal@gmail.com>
Kaan Coekerim <kaan.coekerim@ruhr-uni-bochum.de>
Kaan Coekerim <kaan.coekerim@ruhr-uni-bochum.de> <kaan.coekerim@rub.de>
Ludger Kueperkoch <kueperkoch@igem-energie.de> <kueperkoch@bestec-for-nature.com>
Ludger Kueperkoch <kueperkoch@igem-energie.de> <ludger@quake2.(none)>
Ludger Kueperkoch <kueperkoch@igem-energie.de> <ludger@sauron.bestec-for-nature>
Marc S. Boxberg <marc.boxberg@rub.de>
Marcel Paffrath <marcel.paffrath@ruhr-uni-bochum.de> <marcel.paffrath@rub.de>
Marcel Paffrath <marcel.paffrath@ruhr-uni-bochum.de> <marcel@minos01.geophysik.ruhr-uni-bochum.de>
Marcel Paffrath <marcel.paffrath@ruhr-uni-bochum.de> <marcel@minos02.geophysik.ruhr-uni-bochum.de>
Marcel Paffrath <marcel.paffrath@ruhr-uni-bochum.de> <marcel@minos25.geophysik.ruhr-uni-bochum.de>
Marcel Paffrath <marcel.paffrath@ruhr-uni-bochum.de> <marcel@email.com>
Sally Zimmermann <sally.zimmermann@ruhr-uni-bochum.de>
Sebastian Wehling-Benatelli <sebastian.wehling-benatelli@cgi.com> <sebastianw@minos01.geophysik.ruhr-uni-bochum.de>
Sebastian Wehling-Benatelli <sebastian.wehling-benatelli@cgi.com> <sebastianw@minos02.geophysik.ruhr-uni-bochum.de>
Sebastian Wehling-Benatelli <sebastian.wehling-benatelli@cgi.com> <sebastianw@minos22.geophysik.ruhr-uni-bochum.de>
Sebastian Wehling-Benatelli <sebastian.wehling-benatelli@cgi.com> <sebastian.wehling-benatelli@scisys.de>
Sebastian Wehling-Benatelli <sebastian.wehling-benatelli@cgi.com> <sebastian.wehling@rub.de>
Sebastian Wehling-Benatelli <sebastian.wehling-benatelli@cgi.com> <sebastian.wehling@rub.de>
Sebastian Wehling-Benatelli <sebastian.wehling-benatelli@cgi.com> <DarkBeQst@users.noreply.github.com>
Thomas Moeller <thomas.moeller@rub.de>
Ann-Christin Koch <ann-christin.koch@ruhr-uni-bochum.de> <Ann-Christin.Koch@ruhr-uni-bochum.de>
Sebastian Priebe <sebastian.priebe@rub.de>
+261 -152
View File
@@ -25,6 +25,7 @@ https://www.iconfinder.com/iconsets/flavour
import argparse
import json
import logging
import os
import platform
import shutil
@@ -72,24 +73,25 @@ from pylot.core.util.errors import DatastructureError, \
OverwriteError
from pylot.core.util.connection import checkurl
from pylot.core.util.dataprocessing import Metadata, restitute_data
from pylot.core.util.utils import fnConstructor, getLogin, \
from pylot.core.util.utils import fnConstructor, get_login, \
full_range, readFilterInformation, pick_color_plt, \
pick_linestyle_plt, identifyPhaseID, excludeQualityClasses, \
transform_colors_mpl, transform_colors_mpl_str, getAutoFilteroptions, check_all_obspy, \
check_all_pylot, get_Bool, get_None
check_all_pylot, get_bool, get_none
from pylot.core.util.gui import make_pen
from pylot.core.util.event import Event
from pylot.core.io.location import create_creation_info, create_event
from pylot.core.util.widgets import FilterOptionsDialog, NewEventDlg, \
PylotCanvas, WaveformWidgetPG, PropertiesDlg, HelpForm, createAction, PickDlg, \
ComparisonWidget, TuneAutopicker, PylotParaBox, AutoPickDlg, CanvasWidget, AutoPickWidget, \
CompareEventsWidget, ProgressBarWidget, AddMetadataWidget, SingleTextLineDialog, LogWidget
ComparisonWidget, TuneAutopicker, PylotParameterWidget, AutoPickDlg, CanvasWidget, AutoPickWidget, \
CompareEventsWidget, ProgressBarWidget, AddMetadataWidget, SingleTextLineDialog, LogWidget, PickQualitiesFromXml, \
SpectrogramTab, SearchFileByExtensionDialog
from pylot.core.util.array_map import Array_map
from pylot.core.util.structure import DATASTRUCTURE
from pylot.core.util.thread import Thread, Worker
from pylot.core.util.version import get_git_version as _getVersionString
from pylot.core.io.getEventListFromXML import geteventlistfromxml
from pylot.core.io.getQualitiesfromxml import getQualitiesfromxml
from pylot.core.io.phases import getQualitiesfromxml
from pylot.styles import style_settings
@@ -112,21 +114,19 @@ class MainWindow(QMainWindow):
def __init__(self, parent=None, infile=None, reset_qsettings=False):
super(MainWindow, self).__init__(parent)
# check for default pylot.in-file
if not infile:
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))
if os.path.isfile(infile) == False:
infile = QFileDialog().getOpenFileName(caption='Choose PyLoT-input file')
if infile and os.path.isfile(infile) is False:
infile = QFileDialog().getOpenFileName(caption='Choose PyLoT-input file')[0]
if not os.path.exists(infile[0]):
if not os.path.exists(infile):
QMessageBox.warning(self, "PyLoT Warning",
"No PyLoT-input file declared!")
sys.exit(0)
self.infile = infile[0]
else:
self.infile = infile
"No PyLoT-input file declared! Using default parameters!")
infile = None
self._inputs = PylotParameter(infile)
if not infile:
self._inputs.reset_defaults()
self.infile = infile
self._props = None
self.gain = 1.
@@ -136,7 +136,7 @@ class MainWindow(QMainWindow):
self.project.parameter = self._inputs
self.tap = None
self.apw = None
self.paraBox = None
self.parameterWidget = None
self.array_map = None
self._metadata = Metadata(verbosity=0)
self._eventChanged = [False, False]
@@ -178,6 +178,7 @@ class MainWindow(QMainWindow):
self.autodata = Data(self)
self.fnames = None
self.fnames_comp = None
self._stime = None
# track deleted picks for logging
@@ -187,13 +188,12 @@ class MainWindow(QMainWindow):
self.table_headers = ['', 'Event', 'Time', 'Lat', 'Lon', 'Depth', 'Ml', 'Mw', '[N] MP', '[N] AP', 'Tuning Set',
'Test Set', 'Notes']
# TODO: refactor rootpath to datapath
while True:
try:
if settings.value("user/FullName", None) is None:
fulluser = QInputDialog.getText(self, "Enter Name:", "Full name")
settings.setValue("user/FullName", fulluser)
settings.setValue("user/Login", getLogin())
settings.setValue("user/Login", get_login())
if settings.value("agency_id", None) is None:
agency = QInputDialog.getText(self,
"Enter authority/institution name:",
@@ -250,11 +250,11 @@ class MainWindow(QMainWindow):
self._inputs.reset_defaults()
# check for default pylot.in-file
infile = os.path.join(pylot_config_dir, '.pylot.in')
print('Using default input file {}'.format(infile))
logging.warning('Using default input file {}'.format(infile))
self._inputs.export2File(infile)
self.infile = infile
def setupUi(self):
def setupUi(self, use_logwidget=False):
try:
self.startTime = min(
[tr.stats.starttime for tr in self.data.wfdata])
@@ -480,7 +480,7 @@ class MainWindow(QMainWindow):
"automatic pick "
"data.", False)
self.compare_action.setEnabled(False)
self.qualities_action = self.createAction(parent=self, text='Show pick qualitites...',
self.qualities_action = self.createAction(parent=self, text='Show pick qualities...',
slot=self.pickQualities, shortcut='Alt+Q',
icon=qualities_icon, tip='Histogram of pick qualities')
self.qualities_action.setEnabled(False)
@@ -493,7 +493,6 @@ class MainWindow(QMainWindow):
icon=eventlist_xml_icon,
tip='Create an Eventlist from a XML File')
self.eventlist_xml_action.setEnabled(False)
printAction = self.createAction(self, "&Print event ...",
self.show_event_information, QKeySequence.Print,
print_icon,
@@ -506,6 +505,8 @@ class MainWindow(QMainWindow):
logAction = self.createAction(self, "&Show Log", self.showLogWidget,
tip="""Display Log""")
logAction.setEnabled(use_logwidget)
# create button group for component selection
componentGroup = QActionGroup(self)
@@ -684,10 +685,9 @@ class MainWindow(QMainWindow):
# add scroll area used in case number of traces gets too high
self.wf_scroll_area = QtWidgets.QScrollArea(self)
self.wf_scroll_area.setVisible(False)
self.no_data_label = QLabel('No Data')
self.no_data_label = QLabel('No Data. If data were already loaded, try to select the event again in the eventbox.')
self.no_data_label.setStyleSheet('color: red')
self.no_data_label.setAlignment(Qt.AlignCenter)
# create central matplotlib figure canvas widget
self.init_wfWidget()
@@ -695,14 +695,17 @@ class MainWindow(QMainWindow):
wf_tab = QtWidgets.QWidget(self)
array_tab = QtWidgets.QWidget(self)
events_tab = QtWidgets.QWidget(self)
spectro_tab = QtWidgets.QWidget(self)
# init main widgets layouts
self.wf_layout = QtWidgets.QVBoxLayout()
self.array_layout = QtWidgets.QVBoxLayout()
self.events_layout = QtWidgets.QVBoxLayout()
self.spectro_layout = QtWidgets.QVBoxLayout()
wf_tab.setLayout(self.wf_layout)
array_tab.setLayout(self.array_layout)
events_tab.setLayout(self.events_layout)
spectro_tab.setLayout(self.spectro_layout)
# tighten up layouts inside tabs
for layout in [self.wf_layout, self.array_layout, self.events_layout]:
@@ -713,12 +716,14 @@ class MainWindow(QMainWindow):
self.tabs.addTab(wf_tab, 'Waveform Plot')
self.tabs.addTab(array_tab, 'Array Map')
self.tabs.addTab(events_tab, 'Eventlist')
#self.tabs.addTab(spectro_tab, 'Spectro')
self.wf_layout.addWidget(self.no_data_label)
self.wf_layout.addWidget(self.wf_scroll_area)
self.wf_scroll_area.setWidgetResizable(True)
self.init_array_tab()
self.init_event_table()
#self.init_spectro_tab()
self.tabs.setCurrentIndex(0)
self.eventLabel = QLabel()
@@ -731,13 +736,19 @@ class MainWindow(QMainWindow):
_widget.setLayout(self._main_layout)
_widget.showFullScreen()
if use_logwidget:
self.logwidget = LogWidget(parent=None)
self.logwidget.show()
sys.stdout = self.logwidget.stdout
sys.stderr = self.logwidget.stderr
self.stdout = self.logwidget.stdout
self.stderr = self.logwidget.stderr
self.setCentralWidget(_widget)
# Need to store PickQualities Window somewhere so it doesnt disappear
self.pickQualitiesWindow = None
def init_wfWidget(self):
xlab = self.startTime.strftime('seconds since %Y/%m/%d %H:%M:%S (%Z)')
plottitle = None # "Overview: {0} components ".format(self.getComponent())
@@ -991,17 +1002,16 @@ class MainWindow(QMainWindow):
return
refresh = False
events = self.project.eventlist
sld = SingleTextLineDialog(label='Specify file extension: ', default_text='.xml')
sld = SearchFileByExtensionDialog(label='Specify file extension: ', default_text='.xml',
events=events)
if not sld.exec_():
return
fext = sld.lineEdit.text()
# fext = '.xml'
filenames = sld.getChecked()
for event in events:
path = event.path
eventname = path.split('/')[-1] # or event.pylot_id
filename = os.path.join(path, 'PyLoT_' + eventname + fext)
if os.path.isfile(filename):
self.load_data(filename, draw=False, event=event, overwrite=True)
for filename in filenames:
if os.path.isfile(filename) and event.pylot_id in filename:
self.load_data(filename, draw=False, event=event, ask_user=False, merge_strategy=sld.merge_strategy)
refresh = True
if not refresh:
return
@@ -1010,8 +1020,8 @@ class MainWindow(QMainWindow):
self.fill_eventbox()
self.setDirty(True)
def load_data(self, fname=None, loc=False, draw=True, event=None, overwrite=False):
if not overwrite:
def load_data(self, fname=None, loc=False, draw=True, event=None, ask_user=True, merge_strategy='Overwrite',):
if ask_user:
if not self.okToContinue():
return
if fname is None:
@@ -1020,14 +1030,33 @@ class MainWindow(QMainWindow):
fname = self.filename_from_action(action)
if not fname:
return
if not event:
event = self.get_current_event()
if event.picks and ask_user:
qmb = QMessageBox(self, icon=QMessageBox.Question,
text='Do you want to overwrite the data?',)
overwrite_button = qmb.addButton('Overwrite', QMessageBox.YesRole)
merge_button = qmb.addButton('Merge', QMessageBox.NoRole)
qmb.exec_()
if qmb.clickedButton() == overwrite_button:
merge_strategy = 'Overwrite'
elif qmb.clickedButton() == merge_button:
merge_strategy = 'Merge'
else:
return
data = Data(self, event)
try:
data_new = Data(self, evtdata=str(fname))
# MP MP commented because adding several picks might cause inconsistencies
if merge_strategy == 'Overwrite':
data = data_new
# data += data_new
elif merge_strategy == 'Merge':
data += data_new
else:
raise NotImplementedError(f'Unknown merge strategy: {merge_strategy}')
except ValueError:
qmb = QMessageBox(self, icon=QMessageBox.Question,
text='Warning: Missmatch in event identifiers {} and {}. Continue?'.format(
@@ -1115,16 +1144,19 @@ class MainWindow(QMainWindow):
else:
return
def getWFFnames_from_eventbox(self, eventbox=None):
def getWFFnames_from_eventbox(self, eventbox: str = None, subpath: str = None) -> list:
'''
Return waveform filenames from event in eventbox.
'''
# TODO: add dataStructure class for obspyDMT here, this is just a workaround!
eventpath = self.get_current_event_path(eventbox)
basepath = eventpath.split(os.path.basename(eventpath))[0]
if subpath:
eventpath = os.path.join(eventpath, subpath)
if not os.path.isdir(eventpath):
return []
if self.dataStructure:
if not eventpath:
return
return []
fnames = [os.path.join(eventpath, f) for f in os.listdir(eventpath)]
else:
raise DatastructureError('not specified')
@@ -1168,7 +1200,7 @@ class MainWindow(QMainWindow):
'''
if not self.project:
self.createNewProject()
ed = getExistingDirectories(self, 'Select event directories...')
ed = GetExistingDirectories(self, 'Select event directories...')
if ed.exec_():
eventlist = [event for event in ed.selectedFiles() if not event.endswith('EVENTS-INFO')]
basepath = eventlist[0].split(os.path.basename(eventlist[0]))[0]
@@ -1178,7 +1210,7 @@ class MainWindow(QMainWindow):
with open(eventlist_file, 'r') as infile:
eventlist_subset = [os.path.join(basepath, filename.split('\n')[0]) for filename in
infile.readlines()]
msg = 'Found file "eventlist.txt" in database path. WILL ONLY USE SELECTED EVENTS out of {} events ' \
msg = 'Found file "eventlist.txt" in datapath. WILL ONLY USE SELECTED EVENTS out of {} events ' \
'contained in this subset'
print(msg.format(len(eventlist_subset)))
eventlist = [eventname for eventname in eventlist if eventname in eventlist_subset]
@@ -1203,49 +1235,34 @@ class MainWindow(QMainWindow):
# get path from first event in list and split them
path = eventlist[0]
try:
system_name = platform.system()
if system_name in ["Linux", "Darwin"]:
datapath = os.path.split(path)[0]
dirs = {
'database': path.split('/')[-2],
'datapath': os.path.split(path)[0], # path.split('/')[-3],
'rootpath': '/' + os.path.join(*path.split('/')[:-3])
}
elif system_name == "Windows":
rootpath = path.split('/')[:-3]
rootpath[0] += '/'
dirs = {
# TODO: Arrange path to meet Win standards
'database': path.split('/')[-2],
'datapath': path.split('/')[-3],
'rootpath': os.path.join(*rootpath)
'datapath': datapath,
}
except Exception as e:
dirs = {
'database': '',
'datapath': '',
'rootpath': ''
}
print('Warning: Could not automatically init folder structure. ({})'.format(e))
settings = QSettings()
settings.setValue("data/dataRoot", dirs['datapath']) # d irs['rootpath'])
settings.setValue("data/dataRoot", dirs['datapath'])
settings.sync()
if not self.project.eventlist:
# init parameter object
self.setParameter(show=False)
# hide all parameter (show all needed parameter later)
self.paraBox.hide_parameter()
self.parameterWidget.hide_parameter()
for directory in dirs.keys():
# set parameter
box = self.paraBox.boxes[directory]
self.paraBox.setValue(box, dirs[directory])
box = self.parameterWidget.boxes[directory]
self.parameterWidget.setValue(box, dirs[directory])
# show needed parameter in box
self.paraBox.show_parameter(directory)
dirs_box = self.paraBox.get_groupbox_dialog('Directories')
self.parameterWidget.show_parameter(directory)
dirs_box = self.parameterWidget.get_groupbox_dialog('Directories')
if not dirs_box.exec_():
return
self.project.rootpath = dirs['rootpath']
self.project.datapath = dirs['datapath']
else:
if hasattr(self.project, 'datapath'):
@@ -1254,7 +1271,6 @@ class MainWindow(QMainWindow):
'Datapath missmatch to current project!')
return
else:
self.project.rootpath = dirs['rootpath']
self.project.datapath = dirs['datapath']
self.project.add_eventlist(eventlist)
@@ -1342,11 +1358,10 @@ class MainWindow(QMainWindow):
return True
def modify_project_path(self, new_rootpath):
# TODO: change root to datapath
self.project.rootpath = new_rootpath
self.project.datapath = new_rootpath
for event in self.project.eventlist:
event.rootpath = new_rootpath
event.path = os.path.join(event.rootpath, event.datapath, event.database, event.pylot_id)
event.datapath = new_rootpath
event.path = os.path.join(event.datapath, event.pylot_id)
event.path = event.path.replace('\\', '/')
event.path = event.path.replace('//', '/')
@@ -1369,7 +1384,7 @@ class MainWindow(QMainWindow):
index = eventBox.currentIndex()
tv = QtWidgets.QTableView()
header = tv.horizontalHeader()
header.setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setStretchLastSection(True)
header.hide()
tv.verticalHeader().hide()
@@ -1389,25 +1404,28 @@ class MainWindow(QMainWindow):
for id, event in enumerate(self.project.eventlist):
event_path = event.path
phaseErrors = {'P': self._inputs['timeerrorsP'],
'S': self._inputs['timeerrorsS']}
#phaseErrors = {'P': self._inputs['timeerrorsP'],
# 'S': self._inputs['timeerrorsS']}
ma_props = {'manual': event.pylot_picks,
man_au_picks = {'manual': event.pylot_picks,
'auto': event.pylot_autopicks}
ma_count = {'manual': 0,
'auto': 0}
ma_count_total = {'manual': 0,
'auto': 0}
npicks = {'manual': {'P': 0, 'S': 0},
'auto': {'P': 0, 'S': 0}}
npicks_total = {'manual': {'P': 0, 'S': 0},
'auto': {'P': 0, 'S': 0}}
for ma in ma_props.keys():
if ma_props[ma]:
for picks in ma_props[ma].values():
for ma in man_au_picks.keys():
if man_au_picks[ma]:
for picks in man_au_picks[ma].values():
for phasename, pick in picks.items():
if not type(pick) in [dict, AttribDict]:
continue
phase_ID = identifyPhaseID(phasename)
if not phase_ID in npicks[ma].keys():
continue
if pick.get('spe'):
ma_count[ma] += 1
ma_count_total[ma] += 1
npicks[ma][phase_ID] += 1
npicks_total[ma][phase_ID] += 1
event_ref = event.isRefEvent()
event_test = event.isTestEvent()
@@ -1442,16 +1460,23 @@ class MainWindow(QMainWindow):
if event.dirty:
event_str += '*'
item_path = QStandardItem(event_str)
item_time = QStandardItem('{}'.format(time))
item_time = QStandardItem('{}'.format(time.strftime("%Y-%m-%d %H:%M:%S") if time else ''))
item_lat = QStandardItem('{}'.format(lat))
item_lon = QStandardItem('{}'.format(lon))
item_depth = QStandardItem('{}'.format(depth))
item_localmag = QStandardItem('{}'.format(localmag))
item_momentmag = QStandardItem('{}'.format(momentmag))
item_nmp = QStandardItem('{}({})'.format(ma_count['manual'], ma_count_total['manual']))
item_nmp = QStandardItem()
item_nap = QStandardItem()
item_nmp.setIcon(self.manupicksicon_small)
item_nap = QStandardItem('{}({})'.format(ma_count['auto'], ma_count_total['auto']))
item_nap.setIcon(self.autopicksicon_small)
for picktype, item_np in [('manual', item_nmp), ('auto', item_nap)]:
npicks_str = f"{npicks[picktype]['P']}|{npicks[picktype]['S']}"
#npicks_str += f"({npicks_total[picktype]['P']}/{npicks_total[picktype]['S']})"
item_np.setText(npicks_str)
item_ref = QStandardItem() # str(event_ref))
item_test = QStandardItem() # str(event_test))
if event_ref:
@@ -1530,7 +1555,7 @@ class MainWindow(QMainWindow):
self.set_fname(self.get_data().getEventFileName(), type)
return self.get_fnames(type)
def saveData(self, event=None, directory=None, outformats=['.xml', '.cnv', '.obs', '_focmec.in', '.pha']):
def saveData(self, event=None, directory=None, outformats=None):
'''
Save event data to directory with specified output formats.
:param event: PyLoT Event, if not set current event will be used
@@ -1538,6 +1563,8 @@ class MainWindow(QMainWindow):
:param outformats: str/list of output formats
:return:
'''
if outformats is None:
outformats = ['.xml', '.cnv', '.obs', '_focmec.in', '.pha']
if not event:
event = self.get_current_event()
if not type(outformats) == list:
@@ -1665,16 +1692,55 @@ class MainWindow(QMainWindow):
self.cmpw.show()
def pickQualities(self):
path = self._inputs['rootpath'] + '/' + self._inputs['datapath'] + '/' + self._inputs['database']
getQualitiesfromxml(path)
path = self.get_current_event_path()
(_, plot) = getQualitiesfromxml(path, self._inputs.get('timeerrorsP'), self._inputs.get('timeerrorsS'),plotflag=1)
self.pickQualitiesWindow = PickQualitiesFromXml(figure=plot, path=self.get_current_event_path(),inputVar=self._inputs)
self.pickQualitiesWindow.showUI()
return
# WIP JG
def eventlistXml(self):
path = self._inputs['rootpath'] + '/' + self._inputs['datapath'] + '/' + self._inputs['database']
path = self._inputs['datapath']
outpath = self.project.location[:self.project.location.rfind('/')]
geteventlistfromxml(path, outpath)
return
# WIP JG
def spectogramView(self):
global test
stations = []
names = []
traces = {}
for tr in self.get_data().wfdata.traces:
if not tr.stats.station in stations:
stations.append(tr.stats.station)
names.append(tr.stats.network + '.' + tr.stats.station)
for station in stations:
traces[station] = {}
for ch in ['Z', 'N', 'E']:
for tr in self.get_data().wfdata.select(component=ch).traces:
traces[tr.stats.station][ch] = tr
names.sort()
a = self.get_current_event()
print (self.get_data().wfdata.traces[0])
test = SpectrogramTab(traces, self.get_data().wfdata)
height = self.tabs.widget(0).height()
width = self.tabs.widget(0).width()
self.tabs.setCurrentIndex(3)
figCanvas = test.makeSpecFig(direction=self.dispComponent, height = height, width = width, parent = self.tabs.widget)
return figCanvas
#self.spectro_layout.addWidget()
# self.get_data().wfdata.spectrogram()
# self.tabs.addTab(figCanvas, 'Spectrogram')
# self.tabs[3] = figCanvas
# self.refreshTabs()
# test.show()
def compareMulti(self):
if not self.compareoptions:
return
@@ -1833,6 +1899,7 @@ class MainWindow(QMainWindow):
# which will read in data input twice. Therefore current tab is changed to 0
# in loadProject before calling this function.
self.fill_eventbox()
#print(f'{self.get_current_event()=}')
plotted = False
if self.tabs.currentIndex() == 2:
self.init_event_table()
@@ -1860,6 +1927,12 @@ class MainWindow(QMainWindow):
self.newWF(plot=False)
self.update_obspy_dmt()
self.refresh_array_map()
if self.tabs.currentIndex() == 3:
if self.spectroWidget != None:
self.spectro_layout.removeWidget(self.spectroWidget)
newSpectroWidget = self.spectogramView()
self.spectro_layout.addWidget(newSpectroWidget)
self.spectroWidget = newSpectroWidget
def newWF(self, event=None, plot=True):
'''
@@ -1890,7 +1963,13 @@ class MainWindow(QMainWindow):
def prepareLoadWaveformData(self):
self.fnames = self.getWFFnames_from_eventbox()
self.fnames_syn = []
self.fnames_comp = []
fnames_comp = self.getWFFnames_from_eventbox(subpath='compare')
self.dataPlot.activateCompareOptions(bool(fnames_comp))
if fnames_comp:
if self.dataPlot.comp_checkbox.isChecked():
self.fnames_comp = fnames_comp
eventpath = self.get_current_event_path()
basepath = eventpath.split(os.path.basename(eventpath))[0]
self.obspy_dmt = check_obspydmt_structure(basepath)
@@ -1911,6 +1990,8 @@ class MainWindow(QMainWindow):
# ans = False
settings = QSettings()
# process application events to wait for event items to appear in event box
QApplication.processEvents()
curr_event = self.get_current_event()
if not curr_event:
print('Could not find current event. Try reload?')
@@ -1918,8 +1999,8 @@ class MainWindow(QMainWindow):
if len(curr_event.origins) > 0:
origin_time = curr_event.origins[0].time
tstart = settings.value('tstart') if get_None(settings.value('tstart')) else 0
tstop = settings.value('tstop') if get_None(settings.value('tstop')) else 0
tstart = settings.value('tstart') if get_none(settings.value('tstart')) else 0
tstop = settings.value('tstop') if get_none(settings.value('tstop')) else 0
tstart = origin_time + float(tstart)
tstop = origin_time + float(tstop)
else:
@@ -1927,7 +2008,7 @@ class MainWindow(QMainWindow):
tstop = None
self.data.setWFData(self.fnames,
self.fnames_syn,
self.fnames_comp,
checkRotated=True,
metadata=self.metadata,
tstart=tstart,
@@ -1935,7 +2016,7 @@ class MainWindow(QMainWindow):
def prepareObspyDMT_data(self, eventpath):
qcbox_processed = self.dataPlot.qcombo_processed
qcheckb_syn = self.dataPlot.syn_checkbox
qcheckb_syn = self.dataPlot.comp_checkbox
qcbox_processed.setEnabled(False)
qcheckb_syn.setEnabled(False)
for fpath in os.listdir(eventpath):
@@ -1943,8 +2024,8 @@ class MainWindow(QMainWindow):
if 'syngine' in fpath:
eventpath_syn = os.path.join(eventpath, fpath)
qcheckb_syn.setEnabled(True)
if self.dataPlot.syn_checkbox.isChecked():
self.fnames_syn = [os.path.join(eventpath_syn, filename) for filename in os.listdir(eventpath_syn)]
if self.dataPlot.comp_checkbox.isChecked():
self.fnames_comp = [os.path.join(eventpath_syn, filename) for filename in os.listdir(eventpath_syn)]
if 'processed' in fpath:
qcbox_processed.setEnabled(True)
if qcbox_processed.isEnabled():
@@ -2066,15 +2147,16 @@ class MainWindow(QMainWindow):
def finish_pg_plot(self):
self.getPlotWidget().updateWidget()
plots, gaps = self.wfp_thread.data
plots = self.wfp_thread.data
# do not show plot if no data are given
self.wf_scroll_area.setVisible(len(plots) > 0)
self.no_data_label.setVisible(not len(plots) > 0)
for times, data, times_syn, data_syn in plots:
self.dataPlot.plotWidget.getPlotItem().plot(times, data,
pen=self.dataPlot.pen_linecolor)
self.dataPlot.plotWidget.getPlotItem().plot(np.array(times), np.array(data),
pen=self.dataPlot.pen_linecolor,
skipFiniteCheck=True)
if len(data_syn) > 0:
self.dataPlot.plotWidget.getPlotItem().plot(times_syn, data_syn,
self.dataPlot.plotWidget.getPlotItem().plot(np.array(times_syn), np.array(data_syn),
pen=self.dataPlot.pen_linecolor_syn)
self.dataPlot.reinitMoveProxy()
self.highlight_stations()
@@ -2114,9 +2196,11 @@ class MainWindow(QMainWindow):
if event.pylot_autopicks:
self.drawPicks(picktype='auto')
if event.pylot_picks or event.pylot_autopicks:
if not self._inputs.get('extent') == 'global':
self.locateEventAction.setEnabled(True)
self.qualities_action.setEnabled(True)
self.eventlist_xml_action.setEnabled(True)
if True in self.comparable.values():
self.compare_action.setEnabled(True)
self.draw()
@@ -2224,7 +2308,7 @@ class MainWindow(QMainWindow):
comp = self.getComponent()
title = 'section: {0} components'.format(zne_text[comp])
wfst = self.get_data().getWFData()
wfsyn = self.get_data().getSynWFData()
wfsyn = self.get_data().getAltWFdata()
if self.filterActionP.isChecked() and filter:
self.filterWaveformData(plot=False, phase='P')
elif self.filterActionS.isChecked() and filter:
@@ -2233,14 +2317,14 @@ class MainWindow(QMainWindow):
# wfst += self.get_data().getWFData().select(component=alter_comp)
plotWidget = self.getPlotWidget()
self.adjustPlotHeight()
if get_Bool(settings.value('large_dataset')) == True:
if get_bool(settings.value('large_dataset')) == True:
self.plot_method = 'fast'
else:
self.plot_method = 'normal'
rval = plotWidget.plotWFData(wfdata=wfst, wfsyn=wfsyn, title=title, mapping=False, component=comp,
nth_sample=int(nth_sample), method=self.plot_method, gain=self.gain)
plots, gaps = rval if rval else ([], [])
return plots, gaps
plots = rval if rval else []
return plots
def adjustPlotHeight(self):
if self.pg:
@@ -2343,7 +2427,7 @@ class MainWindow(QMainWindow):
filterS = filteroptions['S']
minP, maxP = filterP.getFreq()
minS, maxS = filterS.getFreq()
self.paraBox.params_to_gui()
self.parameterWidget.params_to_gui()
def getFilterOptions(self):
return self.filteroptions
@@ -2536,17 +2620,20 @@ class MainWindow(QMainWindow):
print("Warning! No network, station, and location info available!")
return
self.update_status('picking on station {0}'.format(station))
data = self.get_data().getOriginalWFData().copy()
wfdata = self.get_data().getOriginalWFData().copy()
wfdata_comp = self.get_data().getAltWFdata().copy()
event = self.get_current_event()
wftype = self.dataPlot.qcombo_processed.currentText() if self.obspy_dmt else None
pickDlg = PickDlg(self, parameter=self._inputs,
data=data.select(station=station),
data=wfdata.select(station=station),
data_compare=wfdata_comp.select(station=station),
station=station, network=network,
location=location,
picks=self.getPicksOnStation(station, 'manual'),
autopicks=self.getPicksOnStation(station, 'auto'),
metadata=self.metadata, event=event,
filteroptions=self.filteroptions, wftype=wftype)
filteroptions=self.filteroptions, wftype=wftype,
show_comp_data=self.dataPlot.comp_checkbox.isChecked())
if self.filterActionP.isChecked():
pickDlg.currentPhase = "P"
pickDlg.filterWFData()
@@ -2659,7 +2746,7 @@ class MainWindow(QMainWindow):
self.init_fig_dict()
# if not self.tap:
# init TuneAutopicker object
self.tap = TuneAutopicker(self)
self.tap = TuneAutopicker(self, self.obspy_dmt)
# first call of update to init tabs with empty canvas
self.update_autopicker()
# connect update signal of TuneAutopicker with update function
@@ -2858,7 +2945,9 @@ class MainWindow(QMainWindow):
self.log_deleted_picks([deleted_pick])
def log_deleted_picks(self, deleted_picks, event_path=None):
''' Log deleted picks to list self.deleted_picks '''
'''
Log deleted picks to list self.deleted_picks
'''
if not event_path:
event_path = self.get_current_event_path()
for deleted_pick in deleted_picks:
@@ -2872,7 +2961,9 @@ class MainWindow(QMainWindow):
self.deleted_picks[event_path].append(deleted_pick)
def dump_deleted_picks(self, event_path):
''' Save deleted picks to json file for event in event_path. Load old file before and merge'''
'''
Save deleted picks to json file for event in event_path. Load old file before and merge
'''
try:
deleted_picks_from_file = self.load_deleted_picks(event_path)
except Exception as e:
@@ -2921,10 +3012,16 @@ class MainWindow(QMainWindow):
event = self.get_current_event()
event.pylot_picks = {}
event.pylot_autopicks = {}
picksdict = picksdict_from_picks(evt=self.get_data().get_evt_data())
picksdict = picksdict_from_picks(evt=self.get_data().get_evt_data(), parameter=self.getParameter())
event.addPicks(picksdict['manual'])
event.addAutopicks(picksdict['auto'])
def getParameter(self):
if hasattr(self.project, 'parameter') and isinstance(self.project.parameter, PylotParameter):
return self.project.parameter
else:
return self._inputs
def drawPicks(self, station=None, picktype=None, stime=None):
# if picktype not specified, draw both
if not stime:
@@ -2999,7 +3096,7 @@ class MainWindow(QMainWindow):
if self.pg:
if spe:
if picks['epp'] and picks['lpp']:
if not self.plot_method == 'fast' and picks['epp'] and picks['lpp']:
pen = make_pen(picktype, phaseID, 'epp', quality)
self.drawnPicks[picktype][station].append(pw.plot([epp, epp], ylims,
alpha=.25, pen=pen, name='EPP'))
@@ -3068,22 +3165,24 @@ class MainWindow(QMainWindow):
lt = locateTool[loctool]
# get working directory
locroot = parameter['nllocroot']
#locroot = 'E:/NLL/src/Insheim'
if locroot is None:
self.PyLoTprefs()
self.locate_event()
ctrfile = os.path.join(locroot, 'run', parameter['ctrfile'])
#ctrfile = 'E:/NLL/src/Insheim/run/Insheim_min1d032016.in'
ttt = parameter['ttpatter']
outfile = parameter['outpatter']
eventname = self.get_current_event_name()
obsdir = os.path.join(self._inputs['rootpath'], self._inputs['datapath'], self._inputs['database'], eventname)
self.saveData(event=self.get_current_event(), directory=obsdir, outformats='.obs')
obsdir = os.path.join(self._inputs['datapath'], eventname)
self.saveData(event=self.get_current_event(), directory=obsdir, outformats=['.obs'])
filename = 'PyLoT_' + eventname
locpath = os.path.join(locroot, 'loc', filename)
phasefile = os.path.join(obsdir, filename + '.obs')
lt.modify_inputs(ctrfile, locroot, filename, phasefile, ttt)
try:
lt.locate(ctrfile)
lt.locate(ctrfile, self._inputs)
except RuntimeError as e:
print(e.message)
# finally:
@@ -3137,7 +3236,7 @@ class MainWindow(QMainWindow):
'''
if checked: pass # dummy argument for QAction trigger signal
self.tabs.setCurrentIndex(1)
# if there is no metadata (invetories is an empty list), just initialize the default empty tab
# if there is no metadata (inventories is an empty list), just initialize the default empty tab
if not self.metadata.inventories:
self.init_array_tab()
return
@@ -3159,6 +3258,15 @@ class MainWindow(QMainWindow):
self.tabs.setCurrentIndex(index)
self.refresh_array_map()
def init_spectro_tab(self):
'''
Init spectrogram tab with currently selected event.
'''
self.spectroWidget = None
#self.spectro_layout.addWidget( self.spectogramView() )
pass
def array_map_thread(self):
'''
Start modal thread to init the array_map object.
@@ -3381,7 +3489,7 @@ class MainWindow(QMainWindow):
self.event_table.setCellWidget(r_index, c_index, item)
header = self.event_table.horizontalHeader()
header.setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setStretchLastSection(True)
self.event_table.cellChanged[int, int].connect(cell_changed)
self.event_table.cellClicked[int, int].connect(cell_clicked)
@@ -3482,15 +3590,19 @@ class MainWindow(QMainWindow):
def calc_magnitude(self):
self.init_metadata()
if not self.metadata:
return None
return []
wf_copy = self.get_data().getWFData().copy()
wf_select = Stream()
# restitute only picked traces
for station in np.unique(self.getPicks('manual').keys() + self.getPicks('auto').keys()):
for station in np.unique(list(self.getPicks('manual').keys()) + list(self.getPicks('auto').keys())):
wf_select += wf_copy.select(station=station)
if not wf_select:
logging.warning('Empty Stream in calc_magnitude. Return.')
return []
corr_wf = restitute_data(wf_select, self.metadata)
# calculate moment magnitude
moment_mag = MomentMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('vp'),
@@ -3555,7 +3667,7 @@ class MainWindow(QMainWindow):
return True
return False
def update_status(self, message, duration=5000):
def update_status(self, message, duration=10000):
self.statusBar().showMessage(message, duration)
if self.get_data() is not None:
if not self.get_current_event() or not self.project.location:
@@ -3608,10 +3720,13 @@ class MainWindow(QMainWindow):
if not self.okToContinue():
return
if not fnm:
dlg = QFileDialog(parent=self)
settings = QSettings()
dir = settings.value('current_project_path')
dlg = QFileDialog(parent=self, directory=dir)
fnm = dlg.getOpenFileName(self, 'Open project file...', filter='Pylot project (*.plp)')[0]
if not fnm:
return
settings.setValue('current_project_path', os.path.split(fnm)[0])
if not os.path.exists(fnm):
QMessageBox.warning(self, 'Could not open file',
'Could not open project file {}. File does not exist.'.format(fnm))
@@ -3622,6 +3737,7 @@ class MainWindow(QMainWindow):
if self.project.parameter:
# do this step to update default parameter on older PyLoT projects
self.project.parameter.reinit_default_parameters()
PylotParameter.check_deprecated_parameters(self.project.parameter)
self._inputs = self.project.parameter
self.updateFilteroptions()
@@ -3667,7 +3783,7 @@ class MainWindow(QMainWindow):
filename = fnm[0] + '.plp'
self.project.parameter = self._inputs
settings = QSettings()
autosaveXML = get_Bool(settings.value('autosaveXML', True))
autosaveXML = get_bool(settings.value('autosaveXML', True))
if autosaveXML:
self.exportEvents()
if not self.project.save(filename): return False
@@ -3691,7 +3807,7 @@ class MainWindow(QMainWindow):
self.metadata.clear_inventory()
self.project.parameter = self._inputs
settings = QSettings()
autosaveXML = get_Bool(settings.value('autosaveXML', True))
autosaveXML = get_bool(settings.value('autosaveXML', True))
if autosaveXML:
self.exportEvents()
if not self.project.save(): return False
@@ -3729,6 +3845,7 @@ class MainWindow(QMainWindow):
def closeEvent(self, event):
if self.okToContinue():
if hasattr(self, 'logwidget'):
self.logwidget.close()
event.accept()
else:
@@ -3738,13 +3855,13 @@ class MainWindow(QMainWindow):
def setParameter(self, checked=0, show=True):
if checked: pass # dummy argument to receive trigger signal (checked) if called by QAction
if not self.paraBox:
self.paraBox = PylotParaBox(self._inputs, parent=self, windowflag=Qt.Window)
self.paraBox.accepted.connect(self._setDirty)
self.paraBox.accepted.connect(self.filterOptionsFromParameter)
if not self.parameterWidget:
self.parameterWidget = PylotParameterWidget(self._inputs, parent=self, windowflag=Qt.Window)
self.parameterWidget.accepted.connect(self._setDirty)
self.parameterWidget.accepted.connect(self.filterOptionsFromParameter)
if show:
self.paraBox.params_to_gui()
self.paraBox.show()
self.parameterWidget.params_to_gui()
self.parameterWidget.show()
def deleteAllAutopicks(self):
qmb = QMessageBox(self, icon=QMessageBox.Question,
@@ -3780,7 +3897,7 @@ class MainWindow(QMainWindow):
def helpHelp(self):
if checkurl():
form = HelpForm(self,
'https://ariadne.geophysik.ruhr-uni-bochum.de/trac/PyLoT/wiki')
'https://github.com/seismology-RUB/PyLoT')
else:
form = HelpForm(self, ':/help.html')
form.show()
@@ -3791,16 +3908,18 @@ class Project(object):
Pickable class containing information of a PyLoT project, like event lists and file locations.
'''
# TODO: remove rootpath
def __init__(self):
self.eventlist = []
self.location = None
self.rootpath = None
self.datapath = None
self.dirty = False
self.parameter = None
self._table = None
@property
def rootpath(self):
return self.datapath
def add_eventlist(self, eventlist):
'''
Add events from an eventlist containing paths to event directories.
@@ -3810,8 +3929,6 @@ class Project(object):
return
for item in eventlist:
event = Event(item)
event.rootpath = self.parameter['rootpath']
event.database = self.parameter['database']
event.datapath = self.parameter['datapath']
if not event.path in self.getPaths():
self.eventlist.append(event)
@@ -3960,13 +4077,13 @@ class Project(object):
return project
class getExistingDirectories(QFileDialog):
class GetExistingDirectories(QFileDialog):
'''
File dialog with possibility to select multiple folders.
'''
def __init__(self, *args):
super(getExistingDirectories, self).__init__(*args)
super(GetExistingDirectories, self).__init__(*args)
self.setOption(self.DontUseNativeDialog, True)
self.setOption(self.ReadOnly, True)
self.setFileMode(self.Directory)
@@ -3992,16 +4109,7 @@ def create_window():
return app, app_created
def main(args=None):
project_filename = None
# args.project_filename = 'C:/Shared/AlpArray/alparray_data/project_alparray_test.plp'
pylot_infile = None
if args:
if args.project_filename:
project_filename = args.project_filename
if args.input_filename:
pylot_infile = args.input_filename
reset_qsettings = args.reset_qsettings
def main(project_filename=None, pylot_infile=None, reset_qsettings=False):
# create the Qt application
pylot_app, app_created = create_window()
@@ -4050,4 +4158,5 @@ if __name__ == "__main__":
parser.add_argument('--reset_qsettings', default=False, action='store_true',
help='reset qsettings (debug option)')
args = parser.parse_args()
sys.exit(main(args))
sys.exit(main(project_filename=args.project_filename, pylot_infile=args.input_filename,
reset_qsettings=args.reset_qsettings))
+21 -34
View File
@@ -11,7 +11,7 @@ PILOT has originally been developed in Mathworks' MatLab. In order to distribute
problems, it has been decided to redevelop the software package in Python. The great work of the ObsPy group allows easy
handling of a bunch of seismic data and PyLoT will benefit a lot compared to the former MatLab version.
The development of PyLoT is part of the joint research project MAGS2 and AlpArray.
The development of PyLoT is part of the joint research project MAGS2, AlpArray and AdriaArray.
## Installation
@@ -24,61 +24,47 @@ It is highly recommended to use Anaconda for a simple creation of a Python insta
Afterwards run (from the PyLoT main directory where the files *requirements.txt* and *pylot.yml* are located)
conda create --name pylot_38 --file requirements.txt
conda env create -f pylot.yml
or
conda env create -f pylot.yml
conda create -c conda-forge --name pylot_311 python=3.11 --file requirements.txt
to create a new Anaconda environment called "pylot_38".
to create a new Anaconda environment called *pylot_311*.
Afterwards activate the environment by typing
conda activate pylot_38
conda activate pylot_311
#### Prerequisites:
In order to run PyLoT you need to install:
- Python 3
- obspy
- pyside2
- pyqtgraph
- cartopy
- joblib
- obspy
- pyaml
- pyqtgraph
- pyside2
(the following are already dependencies of the above packages):
- scipy
- numpy
- matplotlib <= 3.3.x
- matplotlib
#### Some handwork:
PyLoT needs a properties folder on your system to work. It should be situated in your home directory
(on Windows usually C:/Users/*username*):
mkdir ~/.pylot
In the next step you have to copy some files to this directory:
*for local distance seismicity*
cp path-to-pylot/inputs/pylot_local.in ~/.pylot/pylot.in
*for regional distance seismicity*
cp path-to-pylot/inputs/pylot_regional.in ~/.pylot/pylot.in
*for global distance seismicity*
cp path-to-pylot/inputs/pylot_global.in ~/.pylot/pylot.in
and some extra information on error estimates (just needed for reading old PILOT data) and the Richter magnitude scaling
Some extra information on error estimates (just needed for reading old PILOT data) and the Richter magnitude scaling
relation
cp path-to-pylot/inputs/PILOT_TimeErrors.in path-to-pylot/inputs/richter_scaling.data ~/.pylot/
You may need to do some modifications to these files. Especially folder names should be reviewed.
PyLoT has been tested on Mac OSX (10.11), Debian Linux 8 and on Windows 10.
PyLoT has been tested on Mac OSX (10.11), Debian Linux 8 and on Windows 10/11.
## Example Dataset
An example dataset with waveform data, metadata and automatic picks in the obspy-dmt dataset format for testing the teleseismic picking can be found at https://zenodo.org/doi/10.5281/zenodo.13759803
## Release notes
@@ -87,6 +73,7 @@ PyLoT has been tested on Mac OSX (10.11), Debian Linux 8 and on Windows 10.
- event organisation in project files and waveform visualisation
- consistent manual phase picking through predefined SNR dependant zoom level
- consistent automatic phase picking routines using Higher Order Statistics, AIC and Autoregression
- pick correlation correction for teleseismic waveforms
- interactive tuning of auto-pick parameters
- uniform uncertainty estimation from waveform's properties for automatic and manual picks
- pdf representation and comparison of picks taking the uncertainty intrinsically into account
@@ -95,17 +82,17 @@ PyLoT has been tested on Mac OSX (10.11), Debian Linux 8 and on Windows 10.
#### Known issues:
We hope to solve these with the next release.
Current release is still in development progress and has several issues. We are currently lacking manpower, but hope to assess many of the issues in the near future.
## Staff
Original author(s): L. Kueperkoch, S. Wehling-Benatelli, M. Bischoff (PILOT)
Developer(s): M. Paffrath, S. Wehling-Benatelli, L. Kueperkoch, D. Arnold, K. Cökerim, K. Olbert, M. Bischoff, C. Wollin, M. Rische, S. Zimmermann
Developer(s): S. Wehling-Benatelli, L. Kueperkoch, K. Olbert, M. Bischoff, C. Wollin, M. Rische, M. Paffrath
Original author(s): M. Rische, S. Wehling-Benatelli, L. Kueperkoch, M. Bischoff (PILOT)
Others: A. Bruestle, T. Meier, W. Friederich
[ObsPy]: http://github.com/obspy/obspy/wiki
September 2017
March 2025
+18 -23
View File
@@ -28,7 +28,7 @@ from pylot.core.util.dataprocessing import restitute_data, Metadata
from pylot.core.util.defaults import SEPARATOR
from pylot.core.util.event import Event
from pylot.core.util.structure import DATASTRUCTURE
from pylot.core.util.utils import get_None, trim_station_components, check4gapsAndRemove, check4doubled, \
from pylot.core.util.utils import get_none, trim_station_components, check4gapsAndRemove, check4doubled, \
check4rotated
from pylot.core.util.version import get_git_version as _getVersionString
@@ -91,9 +91,9 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
sp=sp_info)
print(splash)
parameter = get_None(parameter)
inputfile = get_None(inputfile)
eventid = get_None(eventid)
parameter = get_none(parameter)
inputfile = get_none(inputfile)
eventid = get_none(eventid)
fig_dict = None
fig_dict_wadatijack = None
@@ -119,13 +119,9 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
obspyDMT_wfpath = input_dict['obspyDMT_wfpath']
if not parameter:
if inputfile:
if not inputfile:
print('Using default input parameter')
parameter = PylotParameter(inputfile)
# iplot = parameter['iplot']
else:
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))
parameter = PylotParameter(infile)
else:
if not type(parameter) == PylotParameter:
print('Wrong input type for parameter: {}'.format(type(parameter)))
@@ -140,11 +136,9 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
if parameter.hasParam('datastructure'):
# getting information on data structure
datastructure = DATASTRUCTURE[parameter.get('datastructure')]()
dsfields = {'root': parameter.get('rootpath'),
'dpath': parameter.get('datapath'),
'dbase': parameter.get('database')}
dsfields = {'dpath': parameter.get('datapath'),}
exf = ['root', 'dpath', 'dbase']
exf = ['dpath']
if parameter['eventID'] != '*' and fnames == 'None':
dsfields['eventID'] = parameter['eventID']
@@ -154,7 +148,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
datastructure.setExpandFields(exf)
# check if default location routine NLLoc is available and all stations are used
if get_None(parameter['nllocbin']) and station == 'all':
if get_none(parameter['nllocbin']) and station == 'all':
locflag = 1
# get NLLoc-root path
nllocroot = parameter.get('nllocroot')
@@ -190,15 +184,15 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
if not input_dict:
# started in production mode
datapath = datastructure.expandDataPath()
if fnames == 'None' and parameter['eventID'] == '*':
if fnames in [None, 'None'] and parameter['eventID'] == '*':
# multiple event processing
# read each event in database
events = [event for event in glob.glob(os.path.join(datapath, '*')) if
(os.path.isdir(event) and not event.endswith('EVENTS-INFO'))]
elif fnames == 'None' and parameter['eventID'] != '*' and not type(parameter['eventID']) == list:
elif fnames in [None, 'None'] and parameter['eventID'] != '*' and not type(parameter['eventID']) == list:
# single event processing
events = glob.glob(os.path.join(datapath, parameter['eventID']))
elif fnames == 'None' and type(parameter['eventID']) == list:
elif fnames in [None, 'None'] and type(parameter['eventID']) == list:
# multiple event processing
events = []
for eventID in parameter['eventID']:
@@ -210,12 +204,10 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
locflag = 2
else:
# started in tune or interactive mode
datapath = os.path.join(parameter['rootpath'],
parameter['datapath'])
datapath = parameter['datapath']
events = []
for eventID in eventid:
events.append(os.path.join(datapath,
parameter['database'],
eventID))
if not events:
@@ -242,12 +234,15 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
data.get_evt_data().path = eventpath
print('Reading event data from filename {}...'.format(filename))
except Exception as e:
if type(e) == FileNotFoundError:
print('Creating new event file.')
else:
print('Could not read event from file {}: {}'.format(filename, e))
data = Data()
pylot_event = Event(eventpath) # event should be path to event directory
data.setEvtData(pylot_event)
if fnames == 'None':
data.setWFData(glob.glob(os.path.join(datapath, event_datapath, '*')))
if fnames in [None, 'None']:
data.setWFData(glob.glob(os.path.join(event_datapath, '*')))
# the following is necessary because within
# multiple event processing no event ID is provided
# in autopylot.in
+10 -4
View File
@@ -3,8 +3,14 @@
#$ -l low
#$ -cwd
#$ -pe smp 40
#$ -l mem=2G
#$ -l h_vmem=2G
#$ -l os=*stretch
##$ -l mem=3G
#$ -l h_vmem=6G
##$ -l os=*stretch
#$ -q low.q@minos11,low.q@minos12,low.q@minos13,low.q@minos14,low.q@minos15
python ./autoPyLoT.py -i /home/marcel/.pylot/pylot_alparray_mantle_corr_stack_0.03-0.5.in -dmt processed -c $NSLOTS
conda activate pylot_311
#python ./autoPyLoT.py -i /home/marcel/.pylot/pylot_adriaarray_m5.0-5.4.in -c 20 -dmt processed
#python ./autoPyLoT.py -i /home/marcel/.pylot/pylot_adriaarray_m5.4-5.7.in -c 20 -dmt processed
#python ./autoPyLoT.py -i /home/marcel/.pylot/pylot_adriaarray_m5.7-6.0.in -c 20 -dmt processed
python ./autoPyLoT.py -i /home/marcel/.pylot/pylot_adriaarray_m6.0-10.0.in -c 20 -dmt processed
+77
View File
@@ -0,0 +1,77 @@
# Pick-Correlation Correction
## Introduction
Currently, the pick-correlation correction algorithm is not accessible from they PyLoT GUI. The main file *pick_correlation_correction.py* is located in the directory *pylot\correlation*.
The program only works for an obspy dmt database structure.
The basic workflow of the algorithm is shown in the following diagram. The first step **(1)** is the normal (automatic) picking procedure in PyLoT. Everything from step **(2)** to **(5)** is part of the correlation correction algorithm.
*Note: The first step is not required in case theoretical onsets are used instead of external picks when the parameter use_taupy_onsets is set to True. However, an existing event quakeML (.xml) file generated by PyLoT might be required for each event in case not external picks are used.*
![images/workflow_stacking.png](images/workflow_stacking.png)
A detailed description of the algorithm can be found in the corresponding publication:
*Paffrath, M., Friederich, W., and the AlpArray and AlpArray-SWATH D Working Groups: Teleseismic P waves at the AlpArray seismic network: wave fronts, absolute travel times and travel-time residuals, Solid Earth, 12, 16351660, https://doi.org/10.5194/se-12-1635-2021, 2021.*
## How to use
To use the program you have to call the main program providing two mandatory arguments: a path to the obspy dmt database folder *dmt_database_path* and the path to the PyLoT infile *pylot.in* for picking of the beam trace:
```python pick_correlation_correction.py dmt_database_path pylot.in```
By default, the parameter file *parameters.yaml* is used. You can use the command line option *--params* to specify a different parameter file and other optional arguments such as *-pd* for plotting detailed information or *-n 4* to use 4 cores for parallel processing:
```python pick_correlation_correction.py dmt_database_path pylot.in --params parameters_adriaarray.yaml -pd -n 4```
## Cross-Correlation Parameters
The program uses the parameters in the file *parameters.yaml* by default. You can use the command line option *--params* to specify a different parameter file. An example of the parameter file is provided in the *correlation\parameters.yaml* file.
In the top level of the parameter file the logging level *logging* can be set, as well as a list of pick phases *pick_phases* (e.g. ['P', 'S']).
For each pick phase the different parameters can be set in the first sub-level of the parameter file, e.g.:
```yaml
logging: info
pick_phases: ['P', 'S']
P:
min_corr_stacking: 0.8
min_corr_export: 0.6
[...]
S:
min_corr_stacking: 0.7
[...]
```
The following parameters are available:
| Parameter Name | Description | Parameter Type |
|--------------------------------|----------------------------------------------------------------------------------------------------|----------------|
| min_corr_stacking | Minimum correlation coefficient for building beam trace | float |
| min_corr_export | Minimum correlation coefficient for pick export | float |
| min_stack | Minimum number of stations for building beam trace | int |
| t_before | Correlation window before reference pick | float |
| t_after | Correlation window after reference pick | float |
| cc_maxlag | Maximum shift for initial correlation | float |
| cc_maxlag2 | Maximum shift for second (final) correlation (also for calculating pick uncertainty) | float |
| initial_pick_outlier_threshold | Threshold for excluding large outliers of initial (AIC) picks | float |
| export_threshold | Automatically exclude all onsets which deviate more than this threshold from corrected taup onsets | float |
| min_picks_export | Minimum number of correlated picks for export | int |
| min_picks_autopylot | Minimum number of reference auto picks to continue with event | int |
| check_RMS | Do RMS check to search for restitution errors (very experimental) | bool |
| use_taupy_onsets | Use taupy onsets as reference picks instead of external picks | bool |
| station_list | Use the following stations as reference for stacking | list[str] |
| use_stacked_trace | Use existing stacked trace if found (spare re-computation) | bool |
| data_dir | obspyDMT data subdirectory (e.g. 'raw', 'processed') | str |
| pickfile_extension | Use quakeML files (PyLoT output) with the following extension | str |
| dt_stacking | Time difference for stacking window (in seconds) | list[float] |
| filter_options | Filter for first correlation (rough) | dict |
| filter_options_final | Filter for second correlation (fine) | dict |
| filter_type | Filter type (e.g. bandpass) | str |
| sampfreq | Sampling frequency (in Hz) | float |
## Example Dataset
An example dataset with waveform data, metadata and automatic picks in the obspy-dmt dataset format for testing can be found at https://zenodo.org/doi/10.5281/zenodo.13759803
-2
View File
@@ -203,8 +203,6 @@ The meaning of the header entries is:
PyLoT GUI starts with an empty project. To add events, use the add event data button. Select one or multiple folders
containing events.
[//]: <> (TODO: explain _Directories: Root path, Data path, Database path_)
### Saving projects
Save the current project from the menu with File->Save project or File->Save project as. PyLoT uses ``.plp`` files to
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+93 -92
View File
@@ -7,121 +7,122 @@ A description of the parameters used for determining automatic picks.
Parameters applied to the traces before picking algorithm starts.
| Name | Description |
|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *P Start*, *P
Stop* | Define time interval relative to trace start time for CF calculation on vertical trace. Value is relative to theoretical onset time if 'Use TauPy' option is enabled in main settings of 'Tune Autopicker' dialogue. |
| *S Start*, *S
Stop* | Define time interval relative to trace start time for CF calculation on horizontal traces. Value is relative to theoretical onset time if 'Use TauPy' option is enabled in main settings of 'Tune Autopicker' dialogue. |
| *Bandpass
Z1* | Filter settings for Butterworth bandpass applied to vertical trace for calculation of initial P pick. |
| *Bandpass
Z2* | Filter settings for Butterworth bandpass applied to vertical trace for calculation of precise P pick. |
| *Bandpass
H1* | Filter settings for Butterworth bandpass applied to horizontal traces for calculation of initial S pick. |
| *Bandpass
H2* | Filter settings for Butterworth bandpass applied to horizontal traces for calculation of precise S pick. |
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *P Start*, *P | |
| Stop* | Define time interval relative to trace start time for CF calculation on vertical trace. Value is relative to theoretical onset time if 'Use TauPy' option is enabled in main settings of 'Tune Autopicker' dialogue. |
| *S Start*, *S | |
| Stop* | Define time interval relative to trace start time for CF calculation on horizontal traces. Value is relative to theoretical onset time if 'Use TauPy' option is enabled in main settings of 'Tune Autopicker' dialogue. |
| *Bandpass | |
| Z1* | Filter settings for Butterworth bandpass applied to vertical trace for calculation of initial P pick. |
| *Bandpass | |
| Z2* | Filter settings for Butterworth bandpass applied to vertical trace for calculation of precise P pick. |
| *Bandpass | |
| H1* | Filter settings for Butterworth bandpass applied to horizontal traces for calculation of initial S pick. |
| *Bandpass | |
| H2* | Filter settings for Butterworth bandpass applied to horizontal traces for calculation of precise S pick. |
## Inital P pick
Parameters used for determination of initial P pick.
| Name | Description |
|--------------|------------------------------------------------------------------------------------------------------------------------------|
| *
tLTA* | Size of gliding LTA window in seconds used for calculation of HOS-CF. |
| *pickwin
P* | Size of time window in seconds in which the minimum of the AIC-CF in front of the maximum of the HOS-CF is determined. |
| *
AICtsmooth* | Average of samples in this time window will be used for smoothing of the AIC-CF. |
| *
checkwinP* | Time in front of the global maximum of the HOS-CF in which to search for a second local extrema. |
| *minfactorP* | Used with *
checkwinP*. If a second local maximum is found, it has to be at least as big as the first maximum * *minfactorP*. |
| *
tsignal* | Time window in seconds after the initial P pick used for determining signal amplitude. |
| *
tnoise* | Time window in seconds in front of initial P pick used for determining noise amplitude. |
| *tsafetey* | Time in seconds between *tsignal* and *
tnoise*. |
| *
tslope* | Time window in seconds after initial P pick in which the slope of the onset is calculated. |
|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| * | |
| tLTA* | Size of gliding LTA window in seconds used for calculation of HOS-CF. |
| *pickwin | |
| P* | Size of time window in seconds in which the minimum of the AIC-CF in front of the maximum of the HOS-CF is determined. |
| * | |
| AICtsmooth* | Average of samples in this time window will be used for smoothing of the AIC-CF. |
| * | |
| checkwinP* | Time in front of the global maximum of the HOS-CF in which to search for a second local extrema. |
| *minfactorP* | Used with * |
| checkwinP*. If a second local maximum is found, it has to be at least as big as the first maximum * *minfactorP*. | |
| * | |
| tsignal* | Time window in seconds after the initial P pick used for determining signal amplitude. |
| * | |
| tnoise* | Time window in seconds in front of initial P pick used for determining noise amplitude. |
| *tsafetey* | Time in seconds between *tsignal* and * |
| tnoise*. | |
| * | |
| tslope* | Time window in seconds after initial P pick in which the slope of the onset is calculated. |
## Inital S pick
Parameters used for determination of initial S pick
| Name | Description |
|---------------|------------------------------------------------------------------------------------------------------------------------------|
| *
tdet1h* | Length of time window in seconds in which AR params of the waveform are determined. |
| *
tpred1h* | Length of time window in seconds in which the waveform is predicted using the AR model. |
| *
AICtsmoothS* | Average of samples in this time window is used for smoothing the AIC-CF. |
| *
pickwinS* | Time window in which the minimum in the AIC-CF in front of the maximum in the ARH-CF is determined. |
| *
checkwinS* | Time in front of the global maximum of the ARH-CF in which to search for a second local extrema. |
| *minfactorP* | Used with *
checkwinS*. If a second local maximum is found, it has to be at least as big as the first maximum * *minfactorS*. |
| *
tsignal* | Time window in seconds after the initial P pick used for determining signal amplitude. |
| *
tnoise* | Time window in seconds in front of initial P pick used for determining noise amplitude. |
| *tsafetey* | Time in seconds between *tsignal* and *
tnoise*. |
| *
tslope* | Time window in seconds after initial P pick in which the slope of the onset is calculated. |
|-------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| * | |
| tdet1h* | Length of time window in seconds in which AR params of the waveform are determined. |
| * | |
| tpred1h* | Length of time window in seconds in which the waveform is predicted using the AR model. |
| * | |
| AICtsmoothS* | Average of samples in this time window is used for smoothing the AIC-CF. |
| * | |
| pickwinS* | Time window in which the minimum in the AIC-CF in front of the maximum in the ARH-CF is determined. |
| * | |
| checkwinS* | Time in front of the global maximum of the ARH-CF in which to search for a second local extrema. |
| *minfactorP* | Used with * |
| checkwinS*. If a second local maximum is found, it has to be at least as big as the first maximum * *minfactorS*. | |
| * | |
| tsignal* | Time window in seconds after the initial P pick used for determining signal amplitude. |
| * | |
| tnoise* | Time window in seconds in front of initial P pick used for determining noise amplitude. |
| *tsafetey* | Time in seconds between *tsignal* and * |
| tnoise*. | |
| * | |
| tslope* | Time window in seconds after initial P pick in which the slope of the onset is calculated. |
## Precise P pick
Parameters used for determination of precise P pick.
| Name | Description |
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *Precalcwin* | Time window in seconds for recalculation of the HOS-CF. The new CF will be two times the size of *
Precalcwin*, since it will be calculated from the initial pick to +/- *Precalcwin*. |
| *
tsmoothP* | Average of samples in this time window will be used for smoothing the second HOS-CF. |
| *
ausP* | Controls artificial uplift of samples during precise picking. A common local minimum of the smoothed and unsmoothed HOS-CF is found when the previous sample is larger or equal to the current sample times (1+*
ausP*). |
|-------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *Precalcwin* | Time window in seconds for recalculation of the HOS-CF. The new CF will be two times the size of * |
| Precalcwin*, since it will be calculated from the initial pick to +/- *Precalcwin*. | |
| * | |
| tsmoothP* | Average of samples in this time window will be used for smoothing the second HOS-CF. |
| * | |
| ausP* | Controls artificial uplift of samples during precise picking. A common local minimum of the smoothed and unsmoothed HOS-CF is found when the previous sample is larger or equal to the current sample times (1+* |
| ausP*). | |
## Precise S pick
Parameters used for determination of precise S pick.
| Name | Description |
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *
tdet2h* | Time window for determination of AR coefficients. |
| *
tpred2h* | Time window in which the waveform is predicted using the determined AR parameters. |
| *Srecalcwin* | Time window for recalculation of ARH-CF. New CF will be calculated from initial pick +/- *
Srecalcwin*. |
| *
tsmoothS* | Average of samples in this time window will be used for smoothing the second ARH-CF. |
| *
ausS* | Controls artificial uplift of samples during precise picking. A common local minimum of the smoothed and unsmoothed ARH-CF is found when the previous sample is larger or equal to the current sample times (1+*
ausS*). |
| *
pickwinS* | Time window around initial pick in which to look for a precise pick. |
|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| * | |
| tdet2h* | Time window for determination of AR coefficients. |
| * | |
| tpred2h* | Time window in which the waveform is predicted using the determined AR parameters. |
| *Srecalcwin* | Time window for recalculation of ARH-CF. New CF will be calculated from initial pick +/- * |
| Srecalcwin*. | |
| * | |
| tsmoothS* | Average of samples in this time window will be used for smoothing the second ARH-CF. |
| * | |
| ausS* | Controls artificial uplift of samples during precise picking. A common local minimum of the smoothed and unsmoothed ARH-CF is found when the previous sample is larger or equal to the current sample times (1+* |
| ausS*). | |
| * | |
| pickwinS* | Time window around initial pick in which to look for a precise pick. |
## Pick quality control
Parameters used for checking quality and integrity of automatic picks.
| Name | Description |
|--------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *
minAICPslope* | Initial P picks with a slope lower than this value will be discared. |
| *
minAICPSNR* | Initial P picks with a SNR below this value will be discarded. |
| *
minAICSslope* | Initial S picks with a slope lower than this value will be discarded. |
| *
minAICSSNR* | Initial S picks with a SNR below this value will be discarded. |
| *minsiglength*, *noisefacor*. *minpercent* | Parameters for checking signal length. In the time window of size *
|--------------------------------------------|-----------------------------------------------------------------------|
| * | |
| minAICPslope* | Initial P picks with a slope lower than this value will be discared. |
| * | |
| minAICPSNR* | Initial P picks with a SNR below this value will be discarded. |
| * | |
| minAICSslope* | Initial S picks with a slope lower than this value will be discarded. |
| * | |
| minAICSSNR* | Initial S picks with a SNR below this value will be discarded. |
| *minsiglength*, *noisefacor*. *minpercent* | Parameters for checking signal length. In the time window of size * |
minsiglength* after the initial P pick *
minpercent* of samples have to be larger than the RMS value. |
| *
@@ -140,11 +141,11 @@ wdttolerance* | Maximum allowed deviation of S onset
Parameters for discrete quality classes.
| Name | Description |
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *
timeerrorsP* | Width of the time windows in seconds between earliest and latest possible pick which represent the quality classes 0, 1, 2, 3 for P onsets. |
| *
timeerrorsS* | Width of the time windows in seconds between earliest and latest possible pick which represent the quality classes 0, 1, 2, 3 for S onsets. |
| *nfacP*, *nfacS* | For determination of latest possible onset time. The time when the signal reaches an amplitude of *
nfac* * mean value of the RMS amplitude in the time window *tnoise* corresponds to the latest possible onset time. |
|--------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| * | |
| timeerrorsP* | Width of the time windows in seconds between earliest and latest possible pick which represent the quality classes 0, 1, 2, 3 for P onsets. |
| * | |
| timeerrorsS* | Width of the time windows in seconds between earliest and latest possible pick which represent the quality classes 0, 1, 2, 3 for S onsets. |
| *nfacP*, *nfacS* | For determination of latest possible onset time. The time when the signal reaches an amplitude of * |
| nfac* * mean value of the RMS amplitude in the time window *tnoise* corresponds to the latest possible onset time. | |
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

+109772 -109081
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -4,10 +4,8 @@
%Parameters are optimized for %extent data sets!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#main settings#
#rootpath# %project path
#datapath# %data path
#database# %name of data base
#eventID# %event ID for single event processing (* for all events found in database)
#eventID# %event ID for single event processing (* for all events found in datapath)
#invdir# %full path to inventory or dataless-seed file
PILOT #datastructure# %choose data structure
True #apverbose# %choose 'True' or 'False' for terminal output
@@ -43,6 +41,7 @@ global #extent# %extent of a
1150.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking
True #use_taup# %use estimated traveltimes from TauPy for calculating windows for CF
iasp91 #taup_model# %define TauPy model for traveltime estimation. Possible values: 1066a, 1066b, ak135, ak135f, herrin, iasp91, jb, prem, pwdk, sp6
P,Pdiff #taup_phases# %Specify possible phases for TauPy (comma separated). See Obspy TauPy documentation for possible values.
0.05 0.5 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz]
0.001 0.5 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz]
0.05 0.5 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz]
+3 -4
View File
@@ -4,10 +4,8 @@
%Parameters are optimized for %extent data sets!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#main settings#
/DATA/Insheim #rootpath# %project path
EVENT_DATA/LOCAL #datapath# %data path
2018.02_Insheim #database# %name of data base
e0006.038.18 #eventID# %event ID for single event processing (* for all events found in database)
/DATA/Insheim/EVENT_DATA/LOCAL/2018.02_Insheim #datapath# %data path
e0006.038.18 #eventID# %event ID for single event processing (* for all events found in datapath)
/DATA/Insheim/STAT_INFO #invdir# %full path to inventory or dataless-seed file
PILOT #datastructure# %choose data structure
True #apverbose# %choose 'True' or 'False' for terminal output
@@ -43,6 +41,7 @@ local #extent# %extent of a
10.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking
False #use_taup# %use estimated traveltimes from TauPy for calculating windows for CF
iasp91 #taup_model# %define TauPy model for traveltime estimation
P #taup_phases# %Specify possible phases for TauPy (comma separated). See Obspy TauPy documentation for possible values.
2.0 20.0 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz]
2.0 30.0 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz]
2.0 10.0 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz]
+2 -3
View File
@@ -4,10 +4,8 @@
%Parameters are optimized for %extent data sets!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#main settings#
#rootpath# %project path
#datapath# %data path
#database# %name of data base
#eventID# %event ID for single event processing (* for all events found in database)
#eventID# %event ID for single event processing (* for all events found in datapath)
#invdir# %full path to inventory or dataless-seed file
PILOT #datastructure# %choose data structure
True #apverbose# %choose 'True' or 'False' for terminal output
@@ -43,6 +41,7 @@ local #extent# %extent of a
10.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking
True #use_taup# %use estimated traveltimes from TauPy for calculating windows for CF
iasp91 #taup_model# %define TauPy model for traveltime estimation
P #taup_phases# %Specify possible phases for TauPy (comma separated). See Obspy TauPy documentation for possible values.
2.0 10.0 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz]
2.0 12.0 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz]
2.0 8.0 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz]
+8 -10
View File
@@ -1,14 +1,12 @@
name: pylot_38
name: pylot_311
channels:
- conda-forge
- defaults
dependencies:
- cartopy=0.20.2
- matplotlib-base=3.3.4
- numpy=1.22.3
- obspy=1.3.0
- pyqtgraph=0.12.4
- pyside2=5.13.2
- python=3.8.12
- qt=5.12.9
- scipy=1.8.0
- cartopy=0.23.0=py311hcf9f919_1
- joblib=1.4.2=pyhd8ed1ab_0
- obspy=1.4.1=py311he736701_3
- pyaml=24.7.0=pyhd8ed1ab_0
- pyqtgraph=0.13.7=pyhd8ed1ab_0
- pyside2=5.15.8=py311h3d699ce_4
- pytest=8.3.2=pyhd8ed1ab_0
+1 -1
View File
@@ -9,7 +9,7 @@ PyLoT - the Python picking and Localization Tool
This python library contains a graphical user interfaces for picking
seismic phases. This software needs ObsPy (http://github.com/obspy/obspy/wiki)
and the Qt4 libraries to be installed first.
and the Qt libraries to be installed first.
PILOT has been developed in Mathworks' MatLab. In order to distribute
PILOT without facing portability problems, it has been decided to re-
+45 -1
View File
@@ -144,6 +144,10 @@ class Magnitude(object):
azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap)
else:
# no scaling necessary
# Temporary fix needs rework
if (len(self.magnitudes.keys()) == 0):
print("Error in local magnitude calculation ")
return None
mag = ope.Magnitude(
mag=np.median([M.mag for M in self.magnitudes.values()]),
magnitude_type=self.type,
@@ -414,6 +418,10 @@ class MomentMagnitude(Magnitude):
distance = degrees2kilometers(a.distance)
azimuth = a.azimuth
incidence = a.takeoff_angle
if not 0. <= incidence <= 360.:
if self.verbose:
print(f'WARNING: Incidence angle outside bounds - {incidence}')
return
w0, fc = calcsourcespec(scopy, onset, self.p_velocity, distance,
azimuth, incidence, self.p_attenuation,
self.plot_flag, self.verbose)
@@ -432,6 +440,40 @@ class MomentMagnitude(Magnitude):
self.event.station_magnitudes.append(magnitude)
self.magnitudes = (station, magnitude)
# WIP JG
def getSourceSpec(self):
for a in self.arrivals:
if a.phase not in 'pP':
continue
# make sure calculating Mo only from reliable onsets
# NLLoc: time_weight = 0 => do not use onset!
if a.time_weight == 0:
continue
pick = a.pick_id.get_referred_object()
station = pick.waveform_id.station_code
if len(self.stream) <= 2:
print("Station:" '{0}'.format(station))
print("WARNING: No instrument corrected data available,"
" no magnitude calculation possible! Go on.")
continue
wf = self.stream.select(station=station)
if not wf:
continue
try:
scopy = wf.copy()
except AssertionError:
print("WARNING: Something's wrong with the data,"
"station {},"
"no calculation of moment magnitude possible! Go on.".format(station))
continue
onset = pick.time
distance = degrees2kilometers(a.distance)
azimuth = a.azimuth
incidence = a.takeoff_angle
w0, fc, plt = calcsourcespec(scopy, onset, self.p_velocity, distance,
azimuth, incidence, self.p_attenuation,
3, self.verbose)
return w0, fc, plt
def calcMoMw(wfstream, w0, rho, vp, delta, verbosity=False):
'''
@@ -602,7 +644,7 @@ def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
N = min(int(np.power(m, 2)), 16384)
# N = int(np.power(m, 2))
y = dt * np.fft.fft(xdat, N)
Y = abs(y[: N / 2])
Y = abs(y[: int(N / 2)])
L = (N - 1) / freq
f = np.arange(0, fny, 1 / L)
@@ -679,6 +721,8 @@ def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
plt.xlabel('Frequency [Hz]')
plt.ylabel('Amplitude [m/Hz]')
plt.grid()
if iplot == 3:
return w0, Fc, plt
plt.show()
try:
input()
+72 -31
View File
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import copy
import logging
import os
from PySide2.QtWidgets import QMessageBox
@@ -19,7 +20,7 @@ from pylot.core.util.errors import FormatError, OverwriteError
from pylot.core.util.event import Event
from pylot.core.util.obspyDMT_interface import qml_from_obspyDMT
from pylot.core.util.utils import fnConstructor, full_range, check4rotated, \
check4gapsAndMerge, trim_station_components
check_for_gaps_and_merge, trim_station_components, check_for_nan
class Data(object):
@@ -35,8 +36,17 @@ class Data(object):
loaded event. Container object holding, e.g. phase arrivals, etc.
"""
def __init__(self, parent=None, evtdata=None):
def __init__(self, parent=None, evtdata=None, picking_parameter=None):
self._parent = parent
if not picking_parameter:
if hasattr(parent, '_inputs'):
picking_parameter = parent._inputs
else:
logging.warning('No picking parameters found! Using default input parameters!!!')
picking_parameter = PylotParameter()
self.picking_parameter = picking_parameter
if self.getParent():
self.comp = parent.getComponent()
else:
@@ -64,7 +74,7 @@ class Data(object):
elif 'LOC' in evtdata:
raise NotImplementedError('PILOT location information '
'read support not yet '
'implemeted.')
'implemented.')
elif 'event.pkl' in evtdata:
evtdata = qml_from_obspyDMT(evtdata)
else:
@@ -260,7 +270,6 @@ class Data(object):
can be a str or a list of strings of ['manual', 'auto', 'origin', 'magnitude']
"""
from pylot.core.util.defaults import OUTPUTFORMATS
if not type(fcheck) == list:
fcheck = [fcheck]
@@ -321,35 +330,61 @@ class Data(object):
if lendiff != 0:
print("Manual as well as automatic picks available. Prefered the {} manual ones!".format(lendiff))
no_uncertainties_p = []
no_uncertainties_s = []
if upperErrors:
# check for pick uncertainties exceeding adjusted upper errors
# Picks with larger uncertainties will not be saved in output file!
for j in range(len(picks)):
for i in range(len(picks_copy)):
if picks_copy[i].phase_hint[0] == 'P':
if (picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[0]) or \
(picks_copy[i].time_errors['uncertainty'] is None):
# Skipping pick if no upper_uncertainty is found and warning user
if picks_copy[i].time_errors['upper_uncertainty'] is None:
#print("{1} P-Pick of station {0} does not have upper_uncertainty and cant be checked".format(
# picks_copy[i].waveform_id.station_code,
# picks_copy[i].method_id))
if not picks_copy[i].waveform_id.station_code in no_uncertainties_p:
no_uncertainties_p.append(picks_copy[i].waveform_id.station_code)
continue
#print ("checking for upper_uncertainty")
if (picks_copy[i].time_errors['uncertainty'] is None) or \
(picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[0]):
print("Uncertainty exceeds or equal adjusted upper time error!")
print("Adjusted uncertainty: {}".format(upperErrors[0]))
print("Pick uncertainty: {}".format(picks_copy[i].time_errors['uncertainty']))
print("{1} P-Pick of station {0} will not be saved in outputfile".format(
picks_copy[i].waveform_id.station_code,
picks_copy[i].method_id))
print("#")
del picks_copy[i]
break
if picks_copy[i].phase_hint[0] == 'S':
if (picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[1]) or \
(picks_copy[i].time_errors['uncertainty'] is None):
# Skipping pick if no upper_uncertainty is found and warning user
if picks_copy[i].time_errors['upper_uncertainty'] is None:
#print("{1} S-Pick of station {0} does not have upper_uncertainty and cant be checked".format(
#picks_copy[i].waveform_id.station_code,
#picks_copy[i].method_id))
if not picks_copy[i].waveform_id.station_code in no_uncertainties_s:
no_uncertainties_s.append(picks_copy[i].waveform_id.station_code)
continue
if (picks_copy[i].time_errors['uncertainty'] is None) or \
(picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[1]):
print("Uncertainty exceeds or equal adjusted upper time error!")
print("Adjusted uncertainty: {}".format(upperErrors[1]))
print("Pick uncertainty: {}".format(picks_copy[i].time_errors['uncertainty']))
print("{1} S-Pick of station {0} will not be saved in outputfile".format(
picks_copy[i].waveform_id.station_code,
picks_copy[i].method_id))
print("#")
del picks_copy[i]
break
for s in no_uncertainties_p:
print("P-Pick of station {0} does not have upper_uncertainty and cant be checked".format(s))
for s in no_uncertainties_s:
print("S-Pick of station {0} does not have upper_uncertainty and cant be checked".format(s))
if fnext == '.obs':
try:
@@ -377,25 +412,19 @@ class Data(object):
not implemented: {1}'''.format(evtformat, e))
if fnext == '.cnv':
try:
velest.export(picks_copy, fnout + fnext, eventinfo=self.get_evt_data())
velest.export(picks_copy, fnout + fnext, self.picking_parameter, eventinfo=self.get_evt_data())
except KeyError as e:
raise KeyError('''{0} export format
not implemented: {1}'''.format(evtformat, e))
if fnext == '_focmec.in':
try:
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))
parameter = PylotParameter(infile)
focmec.export(picks_copy, fnout + fnext, parameter, eventinfo=self.get_evt_data())
focmec.export(picks_copy, fnout + fnext, self.picking_parameter, eventinfo=self.get_evt_data())
except KeyError as e:
raise KeyError('''{0} export format
not implemented: {1}'''.format(evtformat, e))
if fnext == '.pha':
try:
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))
parameter = PylotParameter(infile)
hypodd.export(picks_copy, fnout + fnext, parameter, eventinfo=self.get_evt_data())
hypodd.export(picks_copy, fnout + fnext, self.picking_parameter, eventinfo=self.get_evt_data())
except KeyError as e:
raise KeyError('''{0} export format
not implemented: {1}'''.format(evtformat, e))
@@ -426,20 +455,30 @@ class Data(object):
data.filter(**kwargs)
self.dirty = True
def setWFData(self, fnames, fnames_syn=None, checkRotated=False, metadata=None, tstart=0, tstop=0):
def setWFData(self, fnames, fnames_alt=None, checkRotated=False, metadata=None, tstart=0, tstop=0):
"""
Clear current waveform data and set given waveform data
:param fnames: waveform data names to append
:param fnames_alt: alternative data to show (e.g. synthetic/processed)
:type fnames: list
"""
def check_fname_exists(filenames: list) -> list:
if filenames:
filenames = [fn for fn in filenames if os.path.isfile(fn)]
return filenames
self.wfdata = Stream()
self.wforiginal = None
self.wfsyn = Stream()
self.wf_alt = Stream()
if tstart == tstop:
tstart = tstop = None
self.tstart = tstart
self.tstop = tstop
# remove directories
fnames = check_fname_exists(fnames)
fnames_alt = check_fname_exists(fnames_alt)
# if obspy_dmt:
# wfdir = 'raw'
# self.processed = False
@@ -457,8 +496,8 @@ class Data(object):
# wffnames = fnames
if fnames is not None:
self.appendWFData(fnames)
if fnames_syn is not None:
self.appendWFData(fnames_syn, synthetic=True)
if fnames_alt is not None:
self.appendWFData(fnames_alt, alternative=True)
else:
return False
@@ -466,7 +505,9 @@ class Data(object):
# remove possible underscores in station names
# self.wfdata = remove_underscores(self.wfdata)
# check for gaps and merge
self.wfdata = check4gapsAndMerge(self.wfdata)
self.wfdata, _ = check_for_gaps_and_merge(self.wfdata)
# check for nans
check_for_nan(self.wfdata)
# check for stations with rotated components
if checkRotated and metadata is not None:
self.wfdata = check4rotated(self.wfdata, metadata, verbosity=0)
@@ -478,7 +519,7 @@ class Data(object):
self.dirty = False
return True
def appendWFData(self, fnames, synthetic=False):
def appendWFData(self, fnames, alternative=False):
"""
Read waveform data from fnames and append it to current wf data
:param fnames: waveform data to append
@@ -491,19 +532,19 @@ class Data(object):
if self.dirty:
self.resetWFData()
real_or_syn_data = {True: self.wfsyn,
orig_or_alternative_data = {True: self.wf_alt,
False: self.wfdata}
warnmsg = ''
for fname in set(fnames):
try:
real_or_syn_data[synthetic] += read(fname, starttime=self.tstart, endtime=self.tstop)
orig_or_alternative_data[alternative] += read(fname, starttime=self.tstart, endtime=self.tstop)
except TypeError:
try:
real_or_syn_data[synthetic] += read(fname, format='GSE2', starttime=self.tstart, endtime=self.tstop)
orig_or_alternative_data[alternative] += read(fname, format='GSE2', starttime=self.tstart, endtime=self.tstop)
except Exception as e:
try:
real_or_syn_data[synthetic] += read(fname, format='SEGY', starttime=self.tstart,
orig_or_alternative_data[alternative] += read(fname, format='SEGY', starttime=self.tstart,
endtime=self.tstop)
except Exception as e:
warnmsg += '{0}\n{1}\n'.format(fname, e)
@@ -519,8 +560,8 @@ class Data(object):
def getOriginalWFData(self):
return self.wforiginal
def getSynWFData(self):
return self.wfsyn
def getAltWFdata(self):
return self.wf_alt
def resetWFData(self):
"""
+5 -17
View File
@@ -6,24 +6,14 @@ import numpy as np
Default parameters used for picking
"""
defaults = {'rootpath': {'type': str,
'tooltip': 'project path',
'value': '',
'namestring': 'Root path'},
'datapath': {'type': str,
'tooltip': 'data path',
defaults = {'datapath': {'type': str,
'tooltip': 'path to eventfolders',
'value': '',
'namestring': 'Data path'},
'database': {'type': str,
'tooltip': 'name of data base',
'value': '',
'namestring': 'Database path'},
'eventID': {'type': str,
'tooltip': 'event ID for single event processing (* for all events found in database)',
'value': '',
'tooltip': 'event ID for single event processing (* for all events found in datapath)',
'value': '*',
'namestring': 'Event ID'},
'extent': {'type': str,
@@ -511,7 +501,7 @@ defaults = {'rootpath': {'type': str,
'taup_model': {'type': str,
'tooltip': 'Define TauPy model for traveltime estimation. Possible values: 1066a, 1066b, ak135, ak135f, herrin, iasp91, jb, prem, pwdk, sp6',
'value': None,
'value': 'iasp91',
'namestring': 'TauPy model'},
'taup_phases': {'type': str,
@@ -522,9 +512,7 @@ defaults = {'rootpath': {'type': str,
settings_main = {
'dirs': [
'rootpath',
'datapath',
'database',
'eventID',
'invdir',
'datastructure',
+5
View File
@@ -8,6 +8,11 @@
Edited for use in PyLoT
JG, igem, 01/2022
"""
import os
import argparse
import numpy as np
import matplotlib.pyplot as plt
import glob
from obspy.core.event import read_events
-138
View File
@@ -1,138 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Script to get onset uncertainties from Quakeml.xml files created by PyLoT.
Uncertainties are tranformed into quality classes and visualized via histogram if desired.
Ludger Küperkoch, BESTEC GmbH, 07/2017
rev.: Ludger Küperkoch, igem, 10/2020
Edited for usage in PyLoT: Jeldrik Gaal, igem, 01/2022
"""
import glob
import matplotlib.pyplot as plt
import numpy as np
from obspy.core.event import read_events
def getQualitiesfromxml(path):
# uncertainties
ErrorsP = [0.02, 0.04, 0.08, 0.16]
ErrorsS = [0.04, 0.08, 0.16, 0.32]
Pw0 = []
Pw1 = []
Pw2 = []
Pw3 = []
Pw4 = []
Sw0 = []
Sw1 = []
Sw2 = []
Sw3 = []
Sw4 = []
# data path
dp = path + '/e*/*.xml'
# list of all available xml-files
xmlnames = glob.glob(dp)
# read all onset weights
for names in xmlnames:
print("Getting onset weights from {}".format(names))
cat = read_events(names)
arrivals = cat.events[0].picks
for Pick in arrivals:
if Pick.phase_hint[0] == 'P':
if Pick.time_errors.uncertainty <= ErrorsP[0]:
Pw0.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsP[0] and \
Pick.time_errors.uncertainty <= ErrorsP[1]:
Pw1.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsP[1] and \
Pick.time_errors.uncertainty <= ErrorsP[2]:
Pw2.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsP[2] and \
Pick.time_errors.uncertainty <= ErrorsP[3]:
Pw3.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsP[3]:
Pw4.append(Pick.time_errors.uncertainty)
else:
pass
elif Pick.phase_hint[0] == 'S':
if Pick.time_errors.uncertainty <= ErrorsS[0]:
Sw0.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsS[0] and \
Pick.time_errors.uncertainty <= ErrorsS[1]:
Sw1.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsS[1] and \
Pick.time_errors.uncertainty <= ErrorsS[2]:
Sw2.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsS[2] and \
Pick.time_errors.uncertainty <= ErrorsS[3]:
Sw3.append(Pick.time_errors.uncertainty)
elif Pick.time_errors.uncertainty > ErrorsS[3]:
Sw4.append(Pick.time_errors.uncertainty)
else:
pass
else:
print("Phase hint not defined for picking!")
pass
# get percentage of weights
numPweights = np.sum([len(Pw0), len(Pw1), len(Pw2), len(Pw3), len(Pw4)])
numSweights = np.sum([len(Sw0), len(Sw1), len(Sw2), len(Sw3), len(Sw4)])
try:
P0perc = 100.0 / numPweights * len(Pw0)
except:
P0perc = 0
try:
P1perc = 100.0 / numPweights * len(Pw1)
except:
P1perc = 0
try:
P2perc = 100.0 / numPweights * len(Pw2)
except:
P2perc = 0
try:
P3perc = 100.0 / numPweights * len(Pw3)
except:
P3perc = 0
try:
P4perc = 100.0 / numPweights * len(Pw4)
except:
P4perc = 0
try:
S0perc = 100.0 / numSweights * len(Sw0)
except:
Soperc = 0
try:
S1perc = 100.0 / numSweights * len(Sw1)
except:
S1perc = 0
try:
S2perc = 100.0 / numSweights * len(Sw2)
except:
S2perc = 0
try:
S3perc = 100.0 / numSweights * len(Sw3)
except:
S3perc = 0
try:
S4perc = 100.0 / numSweights * len(Sw4)
except:
S4perc = 0
weights = ('0', '1', '2', '3', '4')
y_pos = np.arange(len(weights))
width = 0.34
p1, = plt.bar(0 - width, P0perc, width, color='black')
p2, = plt.bar(0, S0perc, width, color='red')
plt.bar(y_pos - width, [P0perc, P1perc, P2perc, P3perc, P4perc], width, color='black')
plt.bar(y_pos, [S0perc, S1perc, S2perc, S3perc, S4perc], width, color='red')
plt.ylabel('%')
plt.xticks(y_pos, weights)
plt.xlim([-0.5, 4.5])
plt.xlabel('Qualities')
plt.title('{0} P-Qualities, {1} S-Qualities'.format(numPweights, numSweights))
plt.legend([p1, p2], ['P-Weights', 'S-Weights'])
plt.show()
+33 -3
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import os
from pylot.core.io import default_parameters
from pylot.core.util.errors import ParameterError
@@ -51,10 +53,16 @@ class PylotParameter(object):
self.__parameter = {}
self._verbosity = verbosity
self._parFileCont = {}
# io from parsed arguments alternatively
for key, val in kwargs.items():
self._parFileCont[key] = val
self.from_file()
# if no filename or kwargs given, use default values
if not fnin and not kwargs:
self.reset_defaults()
if fnout:
self.export2File(fnout)
@@ -88,10 +96,10 @@ class PylotParameter(object):
return bool(self.__parameter)
def __getitem__(self, key):
try:
if key in self.__parameter:
return self.__parameter[key]
except:
return None
else:
logging.warning(f'{key} not found in PylotParameter')
def __setitem__(self, key, value):
try:
@@ -418,6 +426,28 @@ class PylotParameter(object):
line = value + name + ttip
fid.write(line)
@staticmethod
def check_deprecated_parameters(parameters):
if parameters.hasParam('database') and parameters.hasParam('rootpath'):
parameters['datapath'] = os.path.join(parameters['rootpath'], parameters['datapath'],
parameters['database'])
logging.warning(
f'Parameters database and rootpath are deprecated. '
f'Tried to merge them to now path: {parameters["datapath"]}.'
)
remove_keys = []
for key in parameters:
if not key in default_parameters.defaults.keys():
remove_keys.append(key)
logging.warning(f'Removing deprecated parameter: {key}')
for key in remove_keys:
del parameters[key]
parameters._settings_main = default_parameters.settings_main
parameters._settings_special_pick = default_parameters.settings_special_pick
class FilterOptions(object):
'''
+3 -3
View File
@@ -1,7 +1,7 @@
from obspy import UTCDateTime
from obspy.core import event as ope
from pylot.core.util.utils import getLogin, getHash
from pylot.core.util.utils import get_login, get_hash
def create_amplitude(pickID, amp, unit, category, cinfo):
@@ -61,7 +61,7 @@ def create_creation_info(agency_id=None, creation_time=None, author=None):
:return:
'''
if author is None:
author = getLogin()
author = get_login()
if creation_time is None:
creation_time = UTCDateTime()
return ope.CreationInfo(agency_id=agency_id, author=author,
@@ -210,7 +210,7 @@ def create_resourceID(timetohash, restype, authority_id=None, hrstr=None):
'''
assert isinstance(timetohash, UTCDateTime), "'timetohash' is not an ObsPy" \
"UTCDateTime object"
hid = getHash(timetohash)
hid = get_hash(timetohash)
if hrstr is None:
resID = ope.ResourceIdentifier(restype + '/' + hid[0:6])
else:
+118 -153
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import glob
import logging
import os
import warnings
@@ -16,8 +17,8 @@ from pylot.core.io.inputs import PylotParameter
from pylot.core.io.location import create_event, \
create_magnitude
from pylot.core.pick.utils import select_for_phase, get_quality_class
from pylot.core.util.utils import getOwner, full_range, four_digits, transformFilterString4Export, \
backtransformFilterString
from pylot.core.util.utils import get_owner, full_range, four_digits, transformFilterString4Export, \
backtransformFilterString, loopIdentifyPhase, identifyPhase
def add_amplitudes(event, amplitudes):
@@ -58,7 +59,7 @@ def readPILOTEvent(phasfn=None, locfn=None, authority_id='RUB', **kwargs):
if phasfn is not None and os.path.isfile(phasfn):
phases = sio.loadmat(phasfn)
phasctime = UTCDateTime(os.path.getmtime(phasfn))
phasauthor = getOwner(phasfn)
phasauthor = get_owner(phasfn)
else:
phases = None
phasctime = None
@@ -66,7 +67,7 @@ def readPILOTEvent(phasfn=None, locfn=None, authority_id='RUB', **kwargs):
if locfn is not None and os.path.isfile(locfn):
loc = sio.loadmat(locfn)
locctime = UTCDateTime(os.path.getmtime(locfn))
locauthor = getOwner(locfn)
locauthor = get_owner(locfn)
else:
loc = None
locctime = None
@@ -217,7 +218,7 @@ def picksdict_from_obs(fn):
return picks
def picksdict_from_picks(evt):
def picksdict_from_picks(evt, parameter=None):
"""
Takes an Event object and return the pick dictionary commonly used within
PyLoT
@@ -230,6 +231,7 @@ def picksdict_from_picks(evt):
'auto': {}
}
for pick in evt.picks:
errors = None
phase = {}
station = pick.waveform_id.station_code
if pick.waveform_id.channel_code is None:
@@ -273,32 +275,28 @@ def picksdict_from_picks(evt):
phase['epp'] = epp
phase['lpp'] = lpp
phase['spe'] = spe
try:
phase['weight'] = weight
except:
# get onset weight from uncertainty
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))
parameter = PylotParameter(infile)
weight = phase.get('weight')
if not weight:
if not parameter:
logging.warning('Using default input parameter')
parameter = PylotParameter()
pick.phase_hint = identifyPhase(pick.phase_hint)
if pick.phase_hint == 'P':
errors = parameter['timeerrorsP']
elif pick.phase_hint == 'S':
errors = parameter['timeerrorsS']
if errors:
weight = get_quality_class(spe, errors)
phase['weight'] = weight
phase['channel'] = channel
phase['network'] = network
phase['picker'] = pick_method
try:
if pick.polarity == 'positive':
phase['fm'] = 'U'
elif pick.polarity == 'negative':
phase['fm'] = 'D'
else:
phase['fm'] = 'N'
except:
print("No FM info available!")
phase['fm'] = 'N'
phase['filter_id'] = filter_id if filter_id is not None else ''
onsets[pick.phase_hint] = phase.copy()
@@ -375,7 +373,6 @@ def picks_from_picksdict(picks, creation_info=None):
def reassess_pilot_db(root_dir, db_dir, out_dir=None, fn_param=None, verbosity=0):
import glob
# TODO: change root to datapath
db_root = os.path.join(root_dir, db_dir)
evt_list = glob.glob1(db_root, 'e????.???.??')
@@ -514,11 +511,11 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
fid = open("%s" % filename, 'w')
# write header
fid.write('# EQEVENT: %s Label: EQ%s Loc: X 0.00 Y 0.00 Z 10.00 OT 0.00 \n' %
(parameter.get('database'), parameter.get('eventID')))
arrivals = chooseArrivals(arrivals) # MP MP what is chooseArrivals? It is not defined anywhere
(parameter.get('datapath'), parameter.get('eventID')))
arrivals = chooseArrivals(arrivals)
for key in arrivals:
# P onsets
if arrivals[key].has_key('P'):
if 'P' in arrivals[key]:
try:
fm = arrivals[key]['P']['fm']
except KeyError as e:
@@ -552,7 +549,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
ss_ms,
pweight))
# S onsets
if arrivals[key].has_key('S') and arrivals[key]['S']['mpp'] is not None:
if 'S' in arrivals[key] and arrivals[key]['S']['mpp'] is not None:
fm = '?'
onset = arrivals[key]['S']['mpp']
year = onset.year
@@ -667,11 +664,11 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
print("Writing phases to %s for HYPOSAT" % filename)
fid = open("%s" % filename, 'w')
# write header
fid.write('%s, event %s \n' % (parameter.get('database'), parameter.get('eventID')))
arrivals = chooseArrivals(arrivals) # MP MP what is chooseArrivals? It is not defined anywhere
fid.write('%s, event %s \n' % (parameter.get('datapath'), parameter.get('eventID')))
arrivals = chooseArrivals(arrivals)
for key in arrivals:
# P onsets
if arrivals[key].has_key('P') and arrivals[key]['P']['mpp'] is not None:
if 'P' in arrivals[key] and arrivals[key]['P']['mpp'] is not None:
if arrivals[key]['P']['weight'] < 4:
Ponset = arrivals[key]['P']['mpp']
pyear = Ponset.year
@@ -700,7 +697,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
fid.write('%-5s P1 %4.0f %02d %02d %02d %02d %05.02f %5.3f -999. 0.00 -999. 0.00\n'
% (key, pyear, pmonth, pday, phh, pmm, Pss, pstd))
# S onsets
if arrivals[key].has_key('S') and arrivals[key]['S']['mpp'] is not None:
if 'S' in arrivals[key] and arrivals[key]['S']['mpp'] is not None:
if arrivals[key]['S']['weight'] < 4:
Sonset = arrivals[key]['S']['mpp']
syear = Sonset.year
@@ -759,17 +756,17 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
cns, eventsource['longitude'], cew, eventsource['depth'], eventinfo.magnitudes[0]['mag'], ifx))
n = 0
# check whether arrivals are dictionaries (autoPyLoT) or pick object (PyLoT)
if isinstance(arrivals, dict) == False:
if isinstance(arrivals, dict) is False:
# convert pick object (PyLoT) into dictionary
evt = ope.Event(resource_id=eventinfo['resource_id'])
evt.picks = arrivals
arrivals = picksdict_from_picks(evt)
arrivals = picksdict_from_picks(evt, parameter=parameter)
# check for automatic and manual picks
# prefer manual picks
usedarrivals = chooseArrivals(arrivals)
for key in usedarrivals:
# P onsets
if usedarrivals[key].has_key('P'):
if 'P' in usedarrivals[key]:
if usedarrivals[key]['P']['weight'] < 4:
n += 1
stat = key
@@ -783,7 +780,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
else:
fid.write('%-4sP%d%6.2f\n' % (stat, Pweight, Prt))
# S onsets
if usedarrivals[key].has_key('S'):
if 'S' in usedarrivals[key]:
if usedarrivals[key]['S']['weight'] < 4:
n += 1
stat = key
@@ -824,18 +821,18 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
# convert pick object (PyLoT) into dictionary
evt = ope.Event(resource_id=eventinfo['resource_id'])
evt.picks = arrivals
arrivals = picksdict_from_picks(evt)
arrivals = picksdict_from_picks(evt, parameter=parameter)
# check for automatic and manual picks
# prefer manual picks
usedarrivals = chooseArrivals(arrivals)
for key in usedarrivals:
if usedarrivals[key].has_key('P'):
if 'P' in usedarrivals[key]:
# P onsets
if usedarrivals[key]['P']['weight'] < 4:
Ponset = usedarrivals[key]['P']['mpp']
Prt = Ponset - stime # onset time relative to source time
fid.write('%s %6.3f 1 P\n' % (key, Prt))
if usedarrivals[key].has_key('S'):
if 'S' in usedarrivals[key]:
# S onsets
if usedarrivals[key]['S']['weight'] < 4:
Sonset = usedarrivals[key]['S']['mpp']
@@ -875,12 +872,12 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
# convert pick object (PyLoT) into dictionary
evt = ope.Event(resource_id=eventinfo['resource_id'])
evt.picks = arrivals
arrivals = picksdict_from_picks(evt)
arrivals = picksdict_from_picks(evt, parameter=parameter)
# check for automatic and manual picks
# prefer manual picks
usedarrivals = chooseArrivals(arrivals)
for key in usedarrivals:
if usedarrivals[key].has_key('P'):
if 'P' in usedarrivals[key]:
if usedarrivals[key]['P']['weight'] < 4 and usedarrivals[key]['P']['fm'] is not None:
stat = key
for i in range(len(picks)):
@@ -962,7 +959,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
arrivals = chooseArrivals(arrivals) # MP MP what is chooseArrivals? It is not defined anywhere
# write phase lines
for key in arrivals:
if arrivals[key].has_key('P'):
if 'P' in arrivals[key]:
if arrivals[key]['P']['weight'] < 4 and arrivals[key]['P']['fm'] is not None:
stat = key
ccode = arrivals[key]['P']['channel']
@@ -1015,7 +1012,6 @@ def chooseArrivals(arrivals):
:return: arrivals but with the manual picks prefered if possible
"""
# If len of arrivals is greater than 2 it comes from autopicking so only autopicks are available
print("=== CHOOSE ===")
if len(arrivals) > 2:
return arrivals
if arrivals['auto'] and arrivals['manual']:
@@ -1056,37 +1052,63 @@ def merge_picks(event, picks):
return event
def getQualitiesfromxml(xmlnames, ErrorsP, ErrorsS, plotflag=1):
def getQualitiesfromxml(path, errorsP, errorsS, plotflag=1, figure=None, verbosity=0):
"""
Script to get onset uncertainties from Quakeml.xml files created by PyLoT.
Uncertainties are tranformed into quality classes and visualized via histogram if desired.
Ludger Küperkoch, BESTEC GmbH, 07/2017
:param xmlnames: list of xml obspy event files containing picks
:type xmlnames: list
:param ErrorsP: time errors of P waves for the four discrete quality classes
:type ErrorsP:
:param ErrorsS: time errors of S waves for the four discrete quality classes
:type ErrorsS:
:param path: path containing xml files
:type path: str
:param errorsP: time errors of P waves for the four discrete quality classes
:type errorsP:
:param errorsS: time errors of S waves for the four discrete quality classes
:type errorsS:
:param plotflag:
:type plotflag:
:return:
:rtype:
"""
from pylot.core.pick.utils import get_quality_class
from pylot.core.util.utils import loopIdentifyPhase, identifyPhase
def calc_perc(uncertainties, ntotal):
''' simple function that calculates percentage of number of uncertainties (list length)'''
if len(uncertainties) == 0:
return 0
else:
return 100. / ntotal * len(uncertainties)
def calc_weight_perc(psweights, weight_ids):
''' calculate percentages of different weights (pick classes!?) of total number of uncertainties of a phase'''
# count total number of list items for this phase
numWeights = np.sum([len(weight) for weight in psweights.values()])
# iterate over all available weights to return a list with percentages for plotting
plot_list = []
for weight_id in weight_ids:
plot_list.append(calc_perc(psweights[weight_id], numWeights))
return plot_list, numWeights
# get all xmlfiles in path (maybe this should be changed to one xml file for this function, selectable via GUI?)
xmlnames = glob.glob(os.path.join(path, '*.xml'))
if len(xmlnames) == 0:
print(f'No files found in path {path}.')
return False
# first define possible phases here
phases = ['P', 'S']
# define possible weights (0-4)
weight_ids = list(range(5))
# put both error lists in a dictionary with P/S key so that amount of code can be halfed by simply using P/S as key
errors = dict(P=errorsP, S=errorsS)
# create dictionaries for each phase (P/S) with a dictionary of empty list for each weight defined in weights
# tuple above
weights = {}
for phase in phases:
weights[phase] = {weight_id: [] for weight_id in weight_ids}
# read all onset weights
Pw0 = []
Pw1 = []
Pw2 = []
Pw3 = []
Pw4 = []
Sw0 = []
Sw1 = []
Sw2 = []
Sw3 = []
Sw4 = []
for names in xmlnames:
print("Getting onset weights from {}".format(names))
cat = read_events(names)
@@ -1094,117 +1116,60 @@ def getQualitiesfromxml(xmlnames, ErrorsP, ErrorsS, plotflag=1):
arrivals = cat.events[0].picks
arrivals_copy = cat_copy.events[0].picks
# Prefere manual picks if qualities are sufficient!
for Pick in arrivals:
if Pick.method_id.id.split('/')[1] == 'manual':
mstation = Pick.waveform_id.station_code
for pick in arrivals:
if pick.method_id.id.split('/')[1] == 'manual':
mstation = pick.waveform_id.station_code
mstation_ext = mstation + '_'
for mpick in arrivals_copy:
phase = identifyPhase(loopIdentifyPhase(Pick.phase_hint))
if phase == 'P':
phase = identifyPhase(loopIdentifyPhase(pick.phase_hint)) # MP MP catch if this fails?
if ((mpick.waveform_id.station_code == mstation) or
(mpick.waveform_id.station_code == mstation_ext)) and \
(mpick.method_id.split('/')[1] == 'auto') and \
(mpick.time_errors['uncertainty'] <= ErrorsP[3]):
del mpick
break
elif phase == 'S':
if ((mpick.waveform_id.station_code == mstation) or
(mpick.waveform_id.station_code == mstation_ext)) and \
(mpick.method_id.split('/')[1] == 'auto') and \
(mpick.time_errors['uncertainty'] <= ErrorsS[3]):
(mpick.method_id.id.split('/')[1] == 'auto') and \
(mpick.time_errors['uncertainty'] <= errors[phase][3]):
del mpick
break
lendiff = len(arrivals) - len(arrivals_copy)
if lendiff != 0:
print("Found manual as well as automatic picks, prefered the {} manual ones!".format(lendiff))
for Pick in arrivals_copy:
phase = identifyPhase(loopIdentifyPhase(Pick.phase_hint))
if phase == 'P':
Pqual = get_quality_class(Pick.time_errors.uncertainty, ErrorsP)
if Pqual == 0:
Pw0.append(Pick.time_errors.uncertainty)
elif Pqual == 1:
Pw1.append(Pick.time_errors.uncertainty)
elif Pqual == 2:
Pw2.append(Pick.time_errors.uncertainty)
elif Pqual == 3:
Pw3.append(Pick.time_errors.uncertainty)
elif Pqual == 4:
Pw4.append(Pick.time_errors.uncertainty)
elif phase == 'S':
Squal = get_quality_class(Pick.time_errors.uncertainty, ErrorsS)
if Squal == 0:
Sw0.append(Pick.time_errors.uncertainty)
elif Squal == 1:
Sw1.append(Pick.time_errors.uncertainty)
elif Squal == 2:
Sw2.append(Pick.time_errors.uncertainty)
elif Squal == 3:
Sw3.append(Pick.time_errors.uncertainty)
elif Squal == 4:
Sw4.append(Pick.time_errors.uncertainty)
else:
for pick in arrivals_copy:
phase = identifyPhase(loopIdentifyPhase(pick.phase_hint))
uncertainty = pick.time_errors.uncertainty
if not uncertainty:
if verbosity > 0:
print('No uncertainty, pick {} invalid!'.format(pick.method_id.id))
continue
# check P/S phase
if phase not in phases:
print("Phase hint not defined for picking!")
pass
continue
qual = get_quality_class(uncertainty, errors[phase])
weights[phase][qual].append(uncertainty)
if plotflag == 0:
Punc = [Pw0, Pw1, Pw2, Pw3, Pw4]
Sunc = [Sw0, Sw1, Sw2, Sw3, Sw4]
return Punc, Sunc
p_unc = [weights['P'][weight_id] for weight_id in weight_ids]
s_unc = [weights['S'][weight_id] for weight_id in weight_ids]
return p_unc, s_unc
else:
if not figure:
fig = plt.figure()
ax = fig.add_subplot(111)
# get percentage of weights
numPweights = np.sum([len(Pw0), len(Pw1), len(Pw2), len(Pw3), len(Pw4)])
numSweights = np.sum([len(Sw0), len(Sw1), len(Sw2), len(Sw3), len(Sw4)])
if len(Pw0) > 0:
P0perc = 100 / numPweights * len(Pw0)
else:
P0perc = 0
if len(Pw1) > 0:
P1perc = 100 / numPweights * len(Pw1)
else:
P1perc = 0
if len(Pw2) > 0:
P2perc = 100 / numPweights * len(Pw2)
else:
P2perc = 0
if len(Pw3) > 0:
P3perc = 100 / numPweights * len(Pw3)
else:
P3perc = 0
if len(Pw4) > 0:
P4perc = 100 / numPweights * len(Pw4)
else:
P4perc = 0
if len(Sw0) > 0:
S0perc = 100 / numSweights * len(Sw0)
else:
S0perc = 0
if len(Sw1) > 0:
S1perc = 100 / numSweights * len(Sw1)
else:
S1perc = 0
if len(Sw2) > 0:
S2perc = 100 / numSweights * len(Sw2)
else:
S2perc = 0
if len(Sw3) > 0:
S3perc = 100 / numSweights * len(Sw3)
else:
S3perc = 0
if len(Sw4) > 0:
S4perc = 100 / numSweights * len(Sw4)
else:
S4perc = 0
listP, numPweights = calc_weight_perc(weights['P'], weight_ids)
listS, numSweights = calc_weight_perc(weights['S'], weight_ids)
weights = ('0', '1', '2', '3', '4')
y_pos = np.arange(len(weights))
y_pos = np.arange(len(weight_ids))
width = 0.34
plt.bar(y_pos - width, [P0perc, P1perc, P2perc, P3perc, P4perc], width, color='black')
plt.bar(y_pos, [S0perc, S1perc, S2perc, S3perc, S4perc], width, color='red')
plt.ylabel('%')
plt.xticks(y_pos, weights)
plt.xlim([-0.5, 4.5])
plt.xlabel('Qualities')
plt.title('{0} P-Qualities, {1} S-Qualities'.format(numPweights, numSweights))
plt.show()
ax.bar(y_pos - width, listP, width, color='black')
ax.bar(y_pos, listS, width, color='red')
ax.set_ylabel('%')
ax.set_xticks(y_pos, weight_ids)
ax.set_xlim([-0.5, 4.5])
ax.set_xlabel('Qualities')
ax.set_title('{0} P-Qualities, {1} S-Qualities'.format(numPweights, numSweights))
if not figure:
fig.show()
return listP, listS
+2 -2
View File
@@ -82,8 +82,8 @@ def locate(fnin, parameter=None):
:param fnin: external program name
:return: None
"""
exe_path = which('NLLoc', parameter)
if exe_path is None:
exe_path = os.path.join(parameter['nllocbin'], 'NLLoc')
if not os.path.isfile(exe_path):
raise NLLocError('NonLinLoc executable not found; check your '
'environment variables')
+95 -53
View File
@@ -20,9 +20,9 @@ from pylot.core.pick.charfuns import CharacteristicFunction
from pylot.core.pick.charfuns import HOScf, AICcf, ARZcf, ARHcf, AR3Ccf
from pylot.core.pick.picker import AICPicker, PragPicker
from pylot.core.pick.utils import checksignallength, checkZ4S, earllatepicker, \
getSNR, fmpicker, checkPonsets, wadaticheck, get_quality_class
getSNR, fmpicker, checkPonsets, wadaticheck, get_quality_class, PickingFailedException, MissingTraceException
from pylot.core.util.utils import getPatternLine, gen_Pool, \
get_Bool, identifyPhaseID, get_None, correct_iplot
get_bool, identifyPhaseID, get_none, correct_iplot
def autopickevent(data, param, iplot=0, fig_dict=None, fig_dict_wadatijack=None, ncores=0, metadata=None, origin=None):
@@ -232,20 +232,6 @@ class PickingContainer:
self.Sflag = 0
class MissingTraceException(ValueError):
"""
Used to indicate missing traces in a obspy.core.stream.Stream object
"""
pass
class PickingFailedException(Exception):
"""
Raised when picking fails due to missing values etc.
"""
pass
class AutopickStation(object):
def __init__(self, wfstream, pickparam, verbose, iplot=0, fig_dict=None, metadata=None, origin=None):
@@ -272,10 +258,14 @@ class AutopickStation(object):
self.pickparams = copy.deepcopy(pickparam)
self.verbose = verbose
self.iplot = correct_iplot(iplot)
self.fig_dict = get_None(fig_dict)
self.fig_dict = get_none(fig_dict)
self.metadata = metadata
self.origin = origin
# initialize TauPy pick estimates
self.estFirstP = None
self.estFirstS = None
# initialize picking results
self.p_results = PickingResults()
self.s_results = PickingResults()
@@ -429,7 +419,7 @@ class AutopickStation(object):
if station_coords is None:
exit_taupy()
raise AttributeError('Warning: Could not find station in metadata')
# TODO raise when metadata.get_coordinates returns None
# TODO: raise when metadata.get_coordinates returns None
source_origin = origin[0]
model = TauPyModel(taup_model)
taup_phases = self.pickparams['taup_phases']
@@ -457,25 +447,27 @@ class AutopickStation(object):
for arr in arrivals:
phases[identifyPhaseID(arr.phase.name)].append(arr)
# get first P and S onsets from arrivals list
estFirstP = 0
estFirstS = 0
arrival_time_p = 0
arrival_time_s = 0
if len(phases['P']) > 0:
arrP, estFirstP = min([(arr, arr.time) for arr in phases['P']], key=lambda t: t[1])
arrP, arrival_time_p = min([(arr, arr.time) for arr in phases['P']], key=lambda t: t[1])
if len(phases['S']) > 0:
arrS, estFirstS = min([(arr, arr.time) for arr in phases['S']], key=lambda t: t[1])
arrS, arrival_time_s = min([(arr, arr.time) for arr in phases['S']], key=lambda t: t[1])
print('autopick: estimated first arrivals for P: {} s, S:{} s after event'
' origin time using TauPy'.format(estFirstP, estFirstS))
return estFirstP, estFirstS
' origin time using TauPy'.format(arrival_time_p, arrival_time_s))
return arrival_time_p, arrival_time_s
def exit_taupy():
"""If taupy failed to calculate theoretical starttimes, picking continues.
For this a clean exit is required, since the P starttime is no longer relative to the theoretic onset but
to the vertical trace starttime, eg. it can't be < 0."""
if self.pickparams["pstart"] < 0:
# TODO here the pickparams is modified, instead of a copy
if self.pickparams["pstart"] < 0:
self.pickparams["pstart"] = 0
if self.pickparams["sstart"] < 0:
self.pickparams["sstart"] = 0
if self.pickparams["use_taup"] is False:
if get_bool(self.pickparams["use_taup"]) is False:
# correct user mistake where a relative cuttime is selected (pstart < 0) but use of taupy is disabled/ has
# not the required parameters
exit_taupy()
@@ -489,16 +481,34 @@ class AutopickStation(object):
raise AttributeError('No source origins given!')
arrivals = create_arrivals(self.metadata, self.origin, self.pickparams["taup_model"])
estFirstP, estFirstS = first_PS_onsets(arrivals)
arrival_P, arrival_S = first_PS_onsets(arrivals)
self.estFirstP = (self.origin[0].time + arrival_P) - self.ztrace.stats.starttime
# modifiy pstart and pstop relative to estimated first P arrival (relative to station time axis)
self.pickparams["pstart"] += (self.origin[0].time + estFirstP) - self.ztrace.stats.starttime
self.pickparams["pstop"] += (self.origin[0].time + estFirstP) - self.ztrace.stats.starttime
self.pickparams["pstart"] += self.estFirstP
self.pickparams["pstop"] += self.estFirstP
print('autopick: CF calculation times respectively:'
' pstart: {} s, pstop: {} s'.format(self.pickparams["pstart"], self.pickparams["pstop"]))
# make sure pstart and pstop are inside the starttime/endtime of vertical trace
self.pickparams["pstart"] = max(self.pickparams["pstart"], 0)
self.pickparams["pstop"] = min(self.pickparams["pstop"], len(self.ztrace) * self.ztrace.stats.delta)
if self.horizontal_traces_exist():
# for the two horizontal components take earliest and latest time to make sure that the s onset is not clipped
# if start and endtime of horizontal traces differ, the s windowsize will automatically increase
trace_s_start = min([self.etrace.stats.starttime, self.ntrace.stats.starttime])
self.estFirstS = (self.origin[0].time + arrival_S) - trace_s_start
# modifiy sstart and sstop relative to estimated first S arrival (relative to station time axis)
self.pickparams["sstart"] += self.estFirstS
self.pickparams["sstop"] += self.estFirstS
print('autopick: CF calculation times respectively:'
' sstart: {} s, sstop: {} s'.format(self.pickparams["sstart"], self.pickparams["sstop"]))
# make sure pstart and pstop are inside the starttime/endtime of horizontal traces
self.pickparams["sstart"] = max(self.pickparams["sstart"], 0)
self.pickparams["sstop"] = min(self.pickparams["sstop"], len(self.ntrace) * self.ntrace.stats.delta,
len(self.etrace) * self.etrace.stats.delta)
def autopickstation(self):
"""
Main function of autopickstation, which calculates P and S picks and returns them in a dictionary.
@@ -508,6 +518,17 @@ class AutopickStation(object):
station's value is the station name on which the picks were calculated.
:rtype: dict
"""
if get_bool(self.pickparams['use_taup']) is True and self.origin is not None:
try:
# modify pstart, pstop, sstart, sstop to be around theoretical onset if taupy should be used,
# else do nothing
self.modify_starttimes_taupy()
except AttributeError as ae:
print(ae)
except MissingTraceException as mte:
print(mte)
try:
self.pick_p_phase()
except MissingTraceException as mte:
@@ -515,7 +536,9 @@ class AutopickStation(object):
except PickingFailedException as pfe:
print(pfe)
if self.horizontal_traces_exist() and self.p_results.weight is not None and self.p_results.weight < 4:
if self.horizontal_traces_exist():
if (self.p_results.weight is not None and self.p_results.weight < 4) or \
get_bool(self.pickparams.get('use_taup')):
try:
self.pick_s_phase()
except MissingTraceException as mte:
@@ -594,6 +617,12 @@ class AutopickStation(object):
# plot tapered trace filtered with bpz2 filter settings
ax1.plot(tdata, self.tr_filt_z_bpz2.data / max(self.tr_filt_z_bpz2.data), color=linecolor, linewidth=0.7,
label='Data')
# plot pickwindows for P
pstart, pstop = self.pickparams['pstart'], self.pickparams['pstop']
if pstart is not None and pstop is not None:
ax1.axvspan(pstart, pstop, color='r', alpha=0.1, zorder=0, label='P window')
if self.estFirstP is not None:
ax1.axvline(self.estFirstP, ls='dashed', color='r', alpha=0.4, label='TauPy estimate')
if self.p_results.weight < 4:
# plot CF of initial onset (HOScf or ARZcf)
ax1.plot(self.cf1.getTimeArray(), self.cf1.getCF() / max(self.cf1.getCF()), 'b', label='CF1')
@@ -631,7 +660,7 @@ class AutopickStation(object):
ax1.set_ylim([-1.5, 1.5])
ax1.set_ylabel('Normalized Counts')
if self.horizontal_traces_exist() and self.s_data.Sflag == 1:
if self.horizontal_traces_exist():# and self.s_data.Sflag == 1:
# plot E trace
ax2 = fig.add_subplot(3, 1, 2, sharex=ax1)
th1data = np.linspace(0, self.etrace.stats.endtime - self.etrace.stats.starttime,
@@ -661,13 +690,14 @@ class AutopickStation(object):
ax2.plot([self.s_results.lpp, self.s_results.lpp], [-1.1, 1.1], 'g--', label='lpp')
ax2.plot([self.s_results.epp, self.s_results.epp], [-1.1, 1.1], 'g--', label='epp')
title = '{channel}, S weight={sweight}, SNR={snr:7.2}, SNR[dB]={snrdb:7.2}'
ax2.set_title(title.format(channel=self.etrace.stats.channel,
sweight=self.s_results.weight,
snr=self.s_results.snr,
snrdb=self.s_results.snrdb))
ax2.set_title(title.format(channel=str(self.etrace.stats.channel),
sweight=str(self.s_results.weight),
snr=str(self.s_results.snr),
snrdb=str(self.s_results.snrdb)))
else:
title = '{channel}, S weight={sweight}, SNR=None, SNR[dB]=None'
ax2.set_title(title.format(channel=self.etrace.stats.channel, sweight=self.s_results.weight))
ax2.set_title(title.format(channel=str(self.etrace.stats.channel),
sweight=str(self.s_results.weight)))
ax2.legend(loc=1)
ax2.set_yticks([])
ax2.set_ylim([-1.5, 1.5])
@@ -697,6 +727,15 @@ class AutopickStation(object):
ax3.plot([refSpick.getpick() - 0.5, refSpick.getpick() + 0.5], [-1.3, -1.3], 'g', linewidth=2)
ax3.plot([self.s_results.lpp, self.s_results.lpp], [-1.1, 1.1], 'g--', label='lpp')
ax3.plot([self.s_results.epp, self.s_results.epp], [-1.1, 1.1], 'g--', label='epp')
# plot pickwindows for S
sstart, sstop = self.pickparams['sstart'], self.pickparams['sstop']
if sstart is not None and sstop is not None:
for axis in [ax2, ax3]:
axis.axvspan(sstart, sstop, color='b', alpha=0.1, zorder=0, label='S window')
if self.estFirstS is not None:
axis.axvline(self.estFirstS, ls='dashed', color='b', alpha=0.4, label='TauPy estimate')
ax3.legend(loc=1)
ax3.set_yticks([])
ax3.set_ylim([-1.5, 1.5])
@@ -795,15 +834,6 @@ class AutopickStation(object):
# save filtered trace in instance for later plotting
self.tr_filt_z_bpz2 = tr_filt
if get_Bool(self.pickparams['use_taup']) is True and self.origin is not None:
try:
# modify pstart, pstop to be around theoretical onset if taupy should be used, else does nothing
self.modify_starttimes_taupy()
except AttributeError as ae:
print(ae)
except MissingTraceException as mte:
print(mte)
Lc = self.pickparams['pstop'] - self.pickparams['pstart']
Lwf = self.ztrace.stats.endtime - self.ztrace.stats.starttime
@@ -828,14 +858,21 @@ class AutopickStation(object):
self.cf1 = None
assert isinstance(self.cf1, CharacteristicFunction), 'cf1 is not set correctly: maybe the algorithm name ({})' \
' is corrupted'.format(self.pickparams["algoP"])
# get the original waveform stream from first CF class cut to identical length as CF for plotting
cut_ogstream = self.cf1.getDataArray(self.cf1.getCut())
# MP: Rename to cf_stream for further use of z_copy and to prevent chaos when z_copy suddenly becomes a cf
# stream and later again a waveform stream
cf_stream = z_copy.copy()
cf_stream[0].data = self.cf1.getCF()
# calculate AIC cf from first cf (either HOS or ARZ)
z_copy[0].data = self.cf1.getCF()
aiccf = AICcf(z_copy, cuttimes)
aiccf = AICcf(cf_stream, cuttimes)
# get preliminary onset time from AIC-CF
self.set_current_figure('aicFig')
aicpick = AICPicker(aiccf, self.pickparams["tsnrz"], self.pickparams["pickwinP"], self.iplot,
Tsmooth=self.pickparams["aictsmooth"], fig=self.current_figure,
linecolor=self.current_linecolor)
linecolor=self.current_linecolor, ogstream=cut_ogstream)
# save aicpick for plotting later
self.p_data.aicpick = aicpick
# add pstart and pstop to aic plot
@@ -848,7 +885,7 @@ class AutopickStation(object):
label='P stop')
ax.legend(loc=1)
Pflag = self._pick_p_quality_control(aicpick, z_copy, tr_filt)
Pflag = self._pick_p_quality_control(aicpick, cf_stream, tr_filt)
# go on with processing if AIC onset passes quality control
slope = aicpick.getSlope()
if not slope: slope = 0
@@ -887,7 +924,7 @@ class AutopickStation(object):
refPpick = PragPicker(self.cf2, self.pickparams["tsnrz"], self.pickparams["pickwinP"], self.iplot,
self.pickparams["ausP"],
self.pickparams["tsmoothP"], aicpick.getpick(), self.current_figure,
self.current_linecolor)
self.current_linecolor, ogstream=cut_ogstream)
# save PragPicker result for plotting
self.p_data.refPpick = refPpick
self.p_results.mpp = refPpick.getpick()
@@ -915,7 +952,7 @@ class AutopickStation(object):
"minFMSNR"]:
# if SNR is high enough, try to determine first motion of onset
self.set_current_figure('fm_picker')
self.p_results.fm = fmpicker(self.zstream, z_copy, self.pickparams["fmpickwin"], self.p_results.mpp,
self.p_results.fm = fmpicker(self.zstream.copy(), z_copy, self.pickparams["fmpickwin"], self.p_results.mpp,
self.iplot, self.current_figure, self.current_linecolor)
msg = "autopickstation: P-weight: {}, SNR: {}, SNR[dB]: {}, Polarity: {}"
msg = msg.format(self.p_results.weight, self.p_results.snr, self.p_results.snrdb, self.p_results.fm)
@@ -1127,7 +1164,9 @@ class AutopickStation(object):
''.format(self.s_results.weight, self.s_results.snr, self.s_results.snrdb))
def pick_s_phase(self):
if get_bool(self.pickparams.get('use_taup')) is True:
cuttimesh = (self.pickparams.get('sstart'), self.pickparams.get('sstop'))
else:
# determine time window for calculating CF after P onset
cuttimesh = self._calculate_cuttimes(type='S', iteration=1)
@@ -1137,11 +1176,14 @@ class AutopickStation(object):
# calculate AIC cf
haiccf = self._calculate_aic_cf_s_pick(cuttimesh)
# get the original waveform stream cut to identical length as CF for plotting
ogstream = haiccf.getDataArray(haiccf.getCut())
# get preliminary onset time from AIC cf
self.set_current_figure('aicARHfig')
aicarhpick = AICPicker(haiccf, self.pickparams["tsnrh"], self.pickparams["pickwinS"], self.iplot,
Tsmooth=self.pickparams["aictsmoothS"], fig=self.current_figure,
linecolor=self.current_linecolor)
linecolor=self.current_linecolor, ogstream=ogstream)
# save pick for later plotting
self.aicarhpick = aicarhpick
+20 -22
View File
@@ -16,10 +16,15 @@ autoregressive prediction: application ot local and regional distances, Geophys.
:author: MAGS2 EP3 working group
"""
import numpy as np
try:
from scipy.signal import tukey
except ImportError:
from scipy.signal.windows import tukey
from obspy.core import Stream
from scipy import signal
from pylot.core.pick.utils import PickingFailedException
class CharacteristicFunction(object):
@@ -55,7 +60,7 @@ class CharacteristicFunction(object):
self.setOrder(order)
self.setFnoise(fnoise)
self.setARdetStep(t2)
self.calcCF(self.getDataArray())
self.calcCF()
self.arpara = np.array([])
self.xpred = np.array([])
@@ -207,17 +212,15 @@ class CharacteristicFunction(object):
data = self.orig_data.copy()
return data
def calcCF(self, data=None):
self.cf = data
def calcCF(self):
pass
class AICcf(CharacteristicFunction):
def calcCF(self, data):
def calcCF(self):
"""
Function to calculate the Akaike Information Criterion (AIC) after Maeda (1985).
:param data: data, time series (whether seismogram or CF)
:type data: tuple
:return: AIC function
:rtype:
"""
@@ -226,7 +229,7 @@ class AICcf(CharacteristicFunction):
ind = np.where(~np.isnan(xnp))[0]
if ind.size:
xnp[:ind[0]] = xnp[ind[0]]
xnp = signal.tukey(len(xnp), alpha=0.05) * xnp
xnp = tukey(len(xnp), alpha=0.05) * xnp
xnp = xnp - np.mean(xnp)
datlen = len(xnp)
k = np.arange(1, datlen)
@@ -255,13 +258,11 @@ class HOScf(CharacteristicFunction):
"""
super(HOScf, self).__init__(data, cut, pickparams["tlta"], pickparams["hosorder"])
def calcCF(self, data):
def calcCF(self):
"""
Function to calculate skewness (statistics of order 3) or kurtosis
(statistics of order 4), using one long moving window, as published
in Kueperkoch et al. (2010).
:param data: data, time series (whether seismogram or CF)
:type data: tuple
in Kueperkoch et al. (2010), or order 2, i.e. STA/LTA.
:return: HOS cf
:rtype:
"""
@@ -316,12 +317,10 @@ class ARZcf(CharacteristicFunction):
super(ARZcf, self).__init__(data, cut, t1=t1, t2=t2, order=pickparams["Parorder"],
fnoise=pickparams["addnoise"])
def calcCF(self, data):
def calcCF(self):
"""
function used to calculate the AR prediction error from a single vertical trace. Can be used to pick
P onsets.
:param data:
:type data: ~obspy.core.stream.Stream
:return: ARZ cf
:rtype:
"""
@@ -452,14 +451,12 @@ class ARHcf(CharacteristicFunction):
super(ARHcf, self).__init__(data, cut, t1=t1, t2=t2, order=pickparams["Sarorder"],
fnoise=pickparams["addnoise"])
def calcCF(self, data):
def calcCF(self):
"""
Function to calculate a characteristic function using autoregressive modelling of the waveform of
both horizontal traces.
The waveform is predicted in a moving time window using the calculated AR parameters. The difference
between the predicted and the actual waveform servers as a characteristic function.
:param data: wavefor stream
:type data: ~obspy.core.stream.Stream
:return: ARH cf
:rtype:
"""
@@ -467,6 +464,9 @@ class ARHcf(CharacteristicFunction):
print('Calculating AR-prediction error from both horizontal traces ...')
xnp = self.getDataArray(self.getCut())
if len(xnp[0]) == 0:
raise PickingFailedException('calcCF: Found empty data trace for cut times. Return')
n0 = np.isnan(xnp[0].data)
if len(n0) > 1:
xnp[0].data[n0] = 0
@@ -605,14 +605,12 @@ class AR3Ccf(CharacteristicFunction):
super(AR3Ccf, self).__init__(data, cut, t1=t1, t2=t2, order=pickparams["Sarorder"],
fnoise=pickparams["addnoise"])
def calcCF(self, data):
def calcCF(self):
"""
Function to calculate a characteristic function using autoregressive modelling of the waveform of
all three traces.
The waveform is predicted in a moving time window using the calculated AR parameters. The difference
between the predicted and the actual waveform servers as a characteristic function
:param data: stream holding all three traces
:type data: ~obspy.core.stream.Stream
:return: AR3C cf
:rtype:
"""
+21 -19
View File
@@ -37,7 +37,8 @@ class AutoPicker(object):
warnings.simplefilter('ignore')
def __init__(self, cf, TSNR, PickWindow, iplot=0, aus=None, Tsmooth=None, Pick1=None, fig=None, linecolor='k'):
def __init__(self, cf, TSNR, PickWindow, iplot=0, aus=None, Tsmooth=None, Pick1=None,
fig=None, linecolor='k', ogstream=None):
"""
Create AutoPicker object
:param cf: characteristic function, on which the picking algorithm is applied
@@ -59,12 +60,15 @@ class AutoPicker(object):
:type fig: `~matplotlib.figure.Figure`
:param linecolor: matplotlib line color string
:type linecolor: str
:param ogstream: original stream (waveform), e.g. for plotting purposes
:type ogstream: `~obspy.core.stream.Stream`
"""
assert isinstance(cf, CharacteristicFunction), "%s is not a CharacteristicFunction object" % str(cf)
self._linecolor = linecolor
self._pickcolor_p = 'b'
self.cf = cf.getCF()
self.ogstream = ogstream
self.Tcf = cf.getTimeArray()
self.Data = cf.getXCF()
self.dt = cf.getIncrement()
@@ -173,12 +177,14 @@ class AICPicker(AutoPicker):
nn = np.isnan(self.cf)
if len(nn) > 1:
self.cf[nn] = 0
# taper AIC-CF to get rid off side maxima
# taper AIC-CF to get rid of side maxima
tap = np.hanning(len(self.cf))
aic = tap * self.cf + max(abs(self.cf))
# smooth AIC-CF
ismooth = int(round(self.Tsmooth / self.dt))
aicsmooth = np.zeros(len(aic))
# MP MP better start with original data than zeros if array shall be smoothed, created artificial value before
# when starting with i in range(1...) loop below and subtracting offset afterwards
aicsmooth = np.copy(aic)
if len(aic) < ismooth:
print('AICPicker: Tsmooth larger than CF!')
return
@@ -188,7 +194,7 @@ class AICPicker(AutoPicker):
ii1 = i - ismooth
aicsmooth[i] = aicsmooth[i - 1] + (aic[i] - aic[ii1]) / ismooth
else:
aicsmooth[i] = np.mean(aic[1: i])
aicsmooth[i] = np.mean(aic[0: i]) # MP MP created np.nan for i=1
# remove offset in AIC function
offset = abs(min(aic) - min(aicsmooth))
aicsmooth = aicsmooth - offset
@@ -197,7 +203,7 @@ class AICPicker(AutoPicker):
# minimum in AIC function
icfmax = np.argmax(cf)
# MP MP testing threshold
# TODO: If this shall be kept, maybe add thresh_factor to pylot parameters
thresh_hit = False
thresh_factor = 0.7
thresh = thresh_factor * cf[icfmax]
@@ -209,7 +215,6 @@ class AICPicker(AutoPicker):
if sample <= cf[index - 1]:
icfmax = index - 1
break
# MP MP ---
# find minimum in AIC-CF front of maximum of HOS/AR-CF
lpickwindow = int(round(self.PickWindow / self.dt))
@@ -315,16 +320,7 @@ class AICPicker(AutoPicker):
plt.close(fig)
return
iislope = islope[0][0:imax + 1]
# MP MP change slope calculation
# get all maxima of aicsmooth
iaicmaxima = argrelmax(aicsmooth)[0]
# get first index of maximum after pickindex (indices saved in iaicmaxima)
aicmax = iaicmaxima[np.where(iaicmaxima > pickindex)[0]]
if len(aicmax) > 0:
iaicmax = aicmax[0]
else:
iaicmax = -1
dataslope = aicsmooth[pickindex: iaicmax]
dataslope = self.Data[0].data[iislope]
# calculate slope as polynomal fit of order 1
xslope = np.arange(0, len(dataslope), 1)
try:
@@ -335,8 +331,8 @@ class AICPicker(AutoPicker):
else:
self.slope = 1 / (len(dataslope) * self.Data[0].stats.delta) * (datafit[-1] - datafit[0])
# normalize slope to maximum of cf to make it unit independent
self.slope /= aicsmooth[iaicmax]
except ValueError as e:
self.slope /= self.Data[0].data[icfmax]
except Exception as e:
print("AICPicker: Problems with data fitting! {}".format(e))
else:
@@ -355,6 +351,12 @@ class AICPicker(AutoPicker):
self.Tcf = self.Tcf[0:len(self.Tcf) - 1]
ax1.plot(self.Tcf, cf / max(cf), color=self._linecolor, linewidth=0.7, label='(HOS-/AR-) Data')
ax1.plot(self.Tcf, aicsmooth / max(aicsmooth), 'r', label='Smoothed AIC-CF')
# plot the original waveform also for evaluation of the CF and pick
if self.ogstream:
data = self.ogstream[0].data
if len(data) == len(self.Tcf):
ax1.plot(self.Tcf, 0.5 * data / max(data), 'k', label='Seismogram', alpha=0.3, zorder=0,
lw=0.5)
if self.Pick is not None:
ax1.plot([self.Pick, self.Pick], [-0.1, 0.5], 'b', linewidth=2, label='AIC-Pick')
ax1.set_xlabel('Time [s] since %s' % self.Data[0].stats.starttime)
@@ -375,7 +377,7 @@ class AICPicker(AutoPicker):
label='Signal Window')
ax2.axvspan(self.Tcf[iislope[0]], self.Tcf[iislope[-1]], color='g', alpha=0.2, lw=0,
label='Slope Window')
ax2.plot(self.Tcf[pickindex: iaicmax], datafit, 'g', linewidth=2,
ax2.plot(self.Tcf[iislope], datafit, 'g', linewidth=2,
label='Slope') # MP MP changed temporarily!
if self.slope is not None:
+46 -33
View File
@@ -15,7 +15,7 @@ import numpy as np
from obspy.core import Stream, UTCDateTime
from scipy.signal import argrelmax
from pylot.core.util.utils import get_Bool, get_None, SetChannelComponents
from pylot.core.util.utils import get_bool, get_none, SetChannelComponents, common_range
def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecolor='k'):
@@ -62,8 +62,8 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecol
plt_flag = 0
try:
iplot = int(iplot)
except:
if get_Bool(iplot):
except ValueError:
if get_bool(iplot):
iplot = 2
else:
iplot = 0
@@ -136,7 +136,7 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecol
PickError = symmetrize_error(diffti_te, diffti_tl)
if iplot > 1:
if get_None(fig) is None:
if get_none(fig) is None:
fig = plt.figure() # iplot)
plt_flag = 1
fig._tight = True
@@ -344,7 +344,7 @@ def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=0, fig=None, linecolor='k'):
print("fmpicker: Found polarity %s" % FM)
if iplot > 1:
if get_None(fig) is None:
if get_none(fig) is None:
fig = plt.figure() # iplot)
plt_flag = 1
fig._tight = True
@@ -816,7 +816,7 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
try:
iplot = int(iplot)
except:
if get_Bool(iplot):
if get_bool(iplot):
iplot = 2
else:
iplot = 0
@@ -828,14 +828,22 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
if len(X) > 1:
# all three components available
# make sure, all components have equal lengths
ilen = min([len(X[0].data), len(X[1].data), len(X[2].data)])
x1 = X[0][0:ilen]
x2 = X[1][0:ilen]
x3 = X[2][0:ilen]
earliest_starttime = min(tr.stats.starttime for tr in X)
cuttimes = common_range(X)
X = X.slice(cuttimes[0], cuttimes[1])
x1, x2, x3 = X[:3]
if not (len(x1) == len(x2) == len(x3)):
raise PickingFailedException('checksignallength: unequal lengths of components!')
# get RMS trace
rms = np.sqrt((np.power(x1, 2) + np.power(x2, 2) + np.power(x3, 2)) / 3)
ilen = len(rms)
dt = earliest_starttime - X[0].stats.starttime
pick -= dt
else:
x1 = X[0].data
x2 = x3 = None
ilen = len(x1)
rms = abs(x1)
@@ -868,12 +876,16 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
returnflag = 0
if iplot > 1:
if get_None(fig) is None:
if get_none(fig) is None:
fig = plt.figure() # iplot)
plt_flag = 1
fig._tight = True
ax = fig.add_subplot(111)
ax.plot(t, rms, color=linecolor, linewidth=0.7, label='RMS Data')
ax.plot(t, x1, 'k', alpha=0.3, lw=0.3, zorder=0)
if x2 is not None and x3 is not None:
ax.plot(t, x2, 'r', alpha=0.3, lw=0.3, zorder=0)
ax.plot(t, x3, 'g', alpha=0.3, lw=0.3, zorder=0)
ax.axvspan(t[inoise[0]], t[inoise[-1]], color='y', alpha=0.2, lw=0, label='Noise Window')
ax.axvspan(t[isignal[0]], t[isignal[-1]], color='b', alpha=0.2, lw=0, label='Signal Window')
ax.plot([t[isignal[0]], t[isignal[len(isignal) - 1]]],
@@ -883,6 +895,7 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
ax.set_xlabel('Time [s] since %s' % X[0].stats.starttime)
ax.set_ylabel('Counts')
ax.set_title('Check for Signal Length, Station %s' % X[0].stats.station)
ax.set_xlim(pickparams["pstart"], pickparams["pstop"])
ax.set_yticks([])
if plt_flag == 1:
fig.show()
@@ -890,6 +903,8 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
input()
except SyntaxError:
pass
except EOFError:
pass
plt.close(fig)
return returnflag
@@ -1130,7 +1145,7 @@ def checkZ4S(X, pick, pickparams, iplot, fig=None, linecolor='k'):
try:
iplot = int(iplot)
except:
if get_Bool(iplot):
if get_bool(iplot):
iplot = 2
else:
iplot = 0
@@ -1211,14 +1226,14 @@ def checkZ4S(X, pick, pickparams, iplot, fig=None, linecolor='k'):
t = np.linspace(diff_dict[key], trace.stats.endtime - trace.stats.starttime + diff_dict[key],
trace.stats.npts)
if i == 0:
if get_None(fig) is None:
if get_none(fig) is None:
fig = plt.figure() # self.iplot) ### WHY? MP MP
plt_flag = 1
ax1 = fig.add_subplot(3, 1, i + 1)
ax = ax1
ax.set_title('CheckZ4S, Station %s' % zdat[0].stats.station)
else:
if get_None(fig) is None:
if get_none(fig) is None:
fig = plt.figure() # self.iplot) ### WHY? MP MP
plt_flag = 1
ax = fig.add_subplot(3, 1, i + 1, sharex=ax1)
@@ -1320,7 +1335,7 @@ def get_quality_class(uncertainty, weight_classes):
:return: quality of pick (0-4)
:rtype: int
"""
if not uncertainty: return max(weight_classes)
if not uncertainty: return len(weight_classes)
try:
# create generator expression containing all indices of values in weight classes that are >= than uncertainty.
# call next on it once to receive first value
@@ -1332,22 +1347,6 @@ def get_quality_class(uncertainty, weight_classes):
return quality
def set_NaNs_to(data, nan_value):
"""
Replace all NaNs in data with nan_value
:param data: array holding data
:type data: `~numpy.ndarray`
:param nan_value: value which all NaNs are set to
:type nan_value: float, int
:return: data array with all NaNs replaced with nan_value
:rtype: `~numpy.ndarray`
"""
nn = np.isnan(data)
if np.any(nn):
data[nn] = nan_value
return data
def taper_cf(cf):
"""
Taper cf data to get rid off of side maximas
@@ -1499,7 +1498,7 @@ def get_pickparams(pickparam):
first_motion_params = dict(zip(first_motion_names, fm_parameter_values))
signal_length_params = dict(zip(signal_length_names, sl_parameter_values))
p_params['use_taup'] = get_Bool(p_params['use_taup'])
p_params['use_taup'] = get_bool(p_params['use_taup'])
return p_params, s_params, first_motion_params, signal_length_params
@@ -1508,7 +1507,7 @@ def getQualityFromUncertainty(uncertainty, Errors):
# set initial quality to 4 (worst) and change only if one condition is hit
quality = 4
if get_None(uncertainty) is None:
if get_none(uncertainty) is None:
return quality
if uncertainty <= Errors[0]:
@@ -1532,3 +1531,17 @@ if __name__ == '__main__':
import doctest
doctest.testmod()
class PickingFailedException(Exception):
"""
Raised when picking fails due to missing values etc.
"""
pass
class MissingTraceException(ValueError):
"""
Used to indicate missing traces in a obspy.core.stream.Stream object
"""
pass
+150 -93
View File
@@ -5,14 +5,18 @@ import traceback
import cartopy.crs as ccrs
import cartopy.feature as cf
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib
import matplotlib.patheffects as PathEffects
import matplotlib.pyplot as plt
import numpy as np
import obspy
from PySide2 import QtWidgets
from PySide2 import QtWidgets, QtGui
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from obspy import UTCDateTime
from pylot.core.util.utils import identifyPhaseID
from scipy.interpolate import griddata
from pylot.core.pick.utils import get_quality_class
@@ -23,10 +27,10 @@ matplotlib.use('Qt5Agg')
class MplCanvas(FigureCanvas):
def __init__(self, parent=None, extern_axes=None, width=5, height=4, dpi=100):
def __init__(self, extern_axes=None, projection=None, width=15, height=5, dpi=100):
if extern_axes is None:
self.fig = plt.figure(figsize=(width, height), dpi=dpi)
self.axes = self.fig.add_subplot(111)
self.axes = self.fig.add_subplot(111, projection=projection)
else:
self.fig = extern_axes.figure
self.axes = extern_axes
@@ -58,25 +62,30 @@ class Array_map(QtWidgets.QWidget):
self.parameter = parameter if parameter else parent._inputs
self.picks_rel = {}
self.picks_rel_mean_corrected = {}
self.marked_stations = []
self.highlighted_stations = []
# call functions to draw everything
self.projection = ccrs.PlateCarree()
self.init_graphics()
self.ax = self.canvas.axes
self.ax.set_adjustable('datalim')
self.init_stations()
self.init_crtpyMap()
self.init_map()
# set original map limits to fall back on when home button is pressed
self.org_xlim = self.canvas.axes.get_xlim()
self.org_ylim = self.canvas.axes.get_ylim()
self.org_xlim = self.ax.get_xlim()
self.org_ylim = self.ax.get_ylim()
# initial map without event
self.canvas.axes.set_xlim(self.org_xlim[0], self.org_xlim[1])
self.canvas.axes.set_ylim(self.org_ylim[0], self.org_ylim[1])
self.ax.set_xlim(self.org_xlim[0], self.org_xlim[1])
self.ax.set_ylim(self.org_ylim[0], self.org_ylim[1])
self._style = None if not hasattr(parent, '_style') else parent._style
self.show()
def init_map(self):
self.init_colormap()
self.connectSignals()
@@ -89,23 +98,24 @@ class Array_map(QtWidgets.QWidget):
# initialize figure elements
if self.extern_plot_axes is None:
self.canvas = MplCanvas(self)
self.plotWidget = FigureCanvas(self.canvas.fig)
self.canvas = MplCanvas(projection=self.projection)
else:
self.canvas = MplCanvas(self, extern_axes=self.extern_plot_axes)
self.plotWidget = FigureCanvas(self.canvas.fig)
self.canvas = MplCanvas(extern_axes=self.extern_plot_axes)
self.plotWidget = self.canvas
# initialize GUI elements
self.status_label = QtWidgets.QLabel()
self.map_reset_button = QtWidgets.QPushButton('Reset Map View')
self.save_map_button = QtWidgets.QPushButton('Save Map')
self.go2eq_button = QtWidgets.QPushButton('Go to Event Location')
self.subtract_mean_cb = QtWidgets.QCheckBox('Subtract mean')
self.main_box = QtWidgets.QVBoxLayout()
self.setLayout(self.main_box)
self.top_row = QtWidgets.QHBoxLayout()
self.main_box.addLayout(self.top_row, 1)
self.main_box.addLayout(self.top_row, 0)
self.comboBox_phase = QtWidgets.QComboBox()
self.comboBox_phase.insertItem(0, 'P')
@@ -124,8 +134,8 @@ class Array_map(QtWidgets.QWidget):
self.cmaps_box = QtWidgets.QComboBox()
self.cmaps_box.setMaxVisibleItems(20)
[self.cmaps_box.addItem(map_name) for map_name in sorted(plt.colormaps())]
# try to set to hsv as default
self.cmaps_box.setCurrentIndex(self.cmaps_box.findText('hsv'))
# try to set to plasma as default
self.cmaps_box.setCurrentIndex(self.cmaps_box.findText('plasma'))
self.top_row.addWidget(QtWidgets.QLabel('Select a phase: '))
self.top_row.addWidget(self.comboBox_phase)
@@ -138,14 +148,15 @@ class Array_map(QtWidgets.QWidget):
self.top_row.addWidget(self.auto_refresh_box)
self.top_row.addWidget(self.refresh_button)
self.main_box.addWidget(self.plotWidget, 1)
self.main_box.addWidget(self.plotWidget, 10)
self.bot_row = QtWidgets.QHBoxLayout()
self.main_box.addLayout(self.bot_row, 0.3)
self.main_box.addLayout(self.bot_row, 0)
self.bot_row.addWidget(QtWidgets.QLabel(''), 5)
self.bot_row.addWidget(self.map_reset_button, 2)
self.bot_row.addWidget(self.go2eq_button, 2)
self.bot_row.addWidget(self.save_map_button, 2)
self.bot_row.addWidget(self.subtract_mean_cb, 0)
self.bot_row.addWidget(self.status_label, 5)
def init_colormap(self):
@@ -153,14 +164,12 @@ class Array_map(QtWidgets.QWidget):
self.init_lat_lon_grid()
def init_crtpyMap(self):
self.canvas.axes.cla()
self.canvas.axes = plt.axes(projection=ccrs.PlateCarree())
self.canvas.axes.add_feature(cf.LAND)
self.canvas.axes.add_feature(cf.OCEAN)
self.canvas.axes.add_feature(cf.COASTLINE, linewidth=1, edgecolor='gray')
self.canvas.axes.add_feature(cf.BORDERS, alpha=0.7)
self.canvas.axes.add_feature(cf.LAKES, alpha=0.7)
self.canvas.axes.add_feature(cf.RIVERS, linewidth=1)
self.ax.add_feature(cf.LAND)
self.ax.add_feature(cf.OCEAN)
self.ax.add_feature(cf.COASTLINE, linewidth=1, edgecolor='gray')
self.ax.add_feature(cf.BORDERS, alpha=0.7)
self.ax.add_feature(cf.LAKES, alpha=0.7)
self.ax.add_feature(cf.RIVERS, linewidth=1)
# parallels and meridians
self.add_merid_paral()
@@ -168,12 +177,8 @@ class Array_map(QtWidgets.QWidget):
self.canvas.fig.tight_layout()
def add_merid_paral(self):
self.gridlines = self.canvas.axes.gridlines(draw_labels=False, alpha=0.6, color='gray',
linewidth=self.linewidth / 2, zorder=7)
# TODO: current cartopy version does not support label removal. Devs are working on it.
# Should be fixed in coming cartopy versions
# self.gridlines.xformatter = LONGITUDE_FORMATTER
# self.gridlines.yformatter = LATITUDE_FORMATTER
self.gridlines = self.ax.gridlines(draw_labels=False, alpha=0.6, color='gray',
linewidth=self.linewidth / 2, zorder=7, crs=ccrs.PlateCarree())
def remove_merid_paral(self):
if len(self.gridlines.xline_artists):
@@ -181,24 +186,24 @@ class Array_map(QtWidgets.QWidget):
self.gridlines.yline_artists[0].remove()
def org_map_view(self):
self.canvas.axes.set_xlim(self.org_xlim[0], self.org_xlim[1])
self.canvas.axes.set_ylim(self.org_ylim[0], self.org_ylim[1])
self.ax.set_xlim(self.org_xlim[0], self.org_xlim[1])
self.ax.set_ylim(self.org_ylim[0], self.org_ylim[1])
# parallels and meridians
self.remove_merid_paral()
self.add_merid_paral()
#self.remove_merid_paral()
#self.add_merid_paral()
self.canvas.axes.figure.canvas.draw_idle()
self.canvas.draw_idle()
def go2eq(self):
if self.eventLoc:
lats, lons = self.eventLoc
self.canvas.axes.set_xlim(lons - 10, lons + 10)
self.canvas.axes.set_ylim(lats - 5, lats + 5)
self.ax.set_xlim(lons - 10, lons + 10)
self.ax.set_ylim(lats - 5, lats + 5)
# parallels and meridians
self.remove_merid_paral()
self.add_merid_paral()
#self.remove_merid_paral()
#self.add_merid_paral()
self.canvas.axes.figure.canvas.draw_idle()
self.canvas.draw_idle()
else:
self.status_label.setText('No event information available')
@@ -212,6 +217,7 @@ class Array_map(QtWidgets.QWidget):
self.map_reset_button.clicked.connect(self.org_map_view)
self.go2eq_button.clicked.connect(self.go2eq)
self.save_map_button.clicked.connect(self.saveFigure)
self.subtract_mean_cb.stateChanged.connect(self.toggle_subtract_mean)
self.plotWidget.mpl_connect('motion_notify_event', self.mouse_moved)
self.plotWidget.mpl_connect('scroll_event', self.mouse_scroll)
@@ -220,21 +226,32 @@ class Array_map(QtWidgets.QWidget):
# set mouse events -----------------------------------------------------
def mouse_moved(self, event):
if not event.inaxes == self.canvas.axes:
if not event.inaxes == self.ax:
return
else:
cont, inds = self.sc.contains(event)
lat = event.ydata
lon = event.xdata
self.status_label.setText('Latitude: {:3.5f}, Longitude: {:3.5f}'.format(lat, lon))
text = f'Longitude: {lon:3.3f}, Latitude: {lat:3.3f}'
if cont:
indices = inds['ind']
text += ' | Station: ' if len(indices) == 1 else ' | Stations: '
text += ' - '.join([self._station_onpick_ids[index] for index in indices[:5]])
if len(indices) > 5:
text += '...'
self.status_label.setText(text)
def mouse_scroll(self, event):
if not event.inaxes == self.canvas.axes:
if not event.inaxes == self.ax:
return
zoom = {'up': 1. / 2., 'down': 2.}
if event.button in zoom:
xlim = self.canvas.axes.get_xlim()
ylim = self.canvas.axes.get_ylim()
xlim = self.ax.get_xlim()
ylim = self.ax.get_ylim()
x, y = event.xdata, event.ydata
@@ -246,24 +263,24 @@ class Array_map(QtWidgets.QWidget):
yb = y - 0.5 * ydiff
yt = y + 0.5 * ydiff
self.canvas.axes.set_xlim(xl, xr)
self.canvas.axes.set_ylim(yb, yt)
self.ax.set_xlim(xl, xr)
self.ax.set_ylim(yb, yt)
# parallels and meridians
self.remove_merid_paral()
self.add_merid_paral()
#self.remove_merid_paral()
#self.add_merid_paral()
self.canvas.axes.figure.canvas.draw_idle()
self.ax.figure.canvas.draw_idle()
def mouseLeftPress(self, event):
if not event.inaxes == self.canvas.axes:
if not event.inaxes == self.ax:
return
self.map_x = event.xdata
self.map_y = event.ydata
self.map_xlim = self.canvas.axes.get_xlim()
self.map_ylim = self.canvas.axes.get_ylim()
self.map_xlim = self.ax.get_xlim()
self.map_ylim = self.ax.get_ylim()
def mouseLeftRelease(self, event):
if not event.inaxes == self.canvas.axes:
if not event.inaxes == self.ax:
return
new_x = event.xdata
new_y = event.ydata
@@ -271,18 +288,21 @@ class Array_map(QtWidgets.QWidget):
dx = new_x - self.map_x
dy = new_y - self.map_y
self.canvas.axes.set_xlim((self.map_xlim[0] - dx, self.map_xlim[1] - dx))
self.canvas.axes.set_ylim(self.map_ylim[0] - dy, self.map_ylim[1] - dy)
self.ax.set_xlim((self.map_xlim[0] - dx, self.map_xlim[1] - dx))
self.ax.set_ylim(self.map_ylim[0] - dy, self.map_ylim[1] - dy)
# parallels and meridians
self.remove_merid_paral()
self.add_merid_paral()
#self.remove_merid_paral()
#self.add_merid_paral()
self.canvas.axes.figure.canvas.draw_idle()
self.ax.figure.canvas.draw_idle()
def onpick(self, event):
btn_msg = {1: ' in selection. Aborted', 2: ' to delete a pick on. Aborted', 3: ' to display info.'}
ind = event.ind
button = event.mouseevent.button
if ind == []:
msg_reason = None
if len(ind) > 1:
self._parent.update_status(f'Found more than one station {btn_msg.get(button)}')
return
if button == 1:
self.openPickDlg(ind)
@@ -354,12 +374,6 @@ class Array_map(QtWidgets.QWidget):
def get_max_from_stations(self, key):
return self._from_dict(max, key)
def get_min_from_picks(self):
return min(self.picks_rel.values())
def get_max_from_picks(self):
return max(self.picks_rel.values())
def current_picks_dict(self):
picktype = self.comboBox_am.currentText().split(' ')[0]
auto_manu = {'auto': self.autopicks_dict,
@@ -385,7 +399,14 @@ class Array_map(QtWidgets.QWidget):
try:
station_name = st_id.split('.')[-1]
# current_picks_dict: auto or manual
pick = self.current_picks_dict()[station_name][phase]
station_picks = self.current_picks_dict().get(station_name)
if not station_picks:
continue
for phase_hint, pick in station_picks.items():
if identifyPhaseID(phase_hint) == phase:
break
else:
continue
if pick['picker'] == 'auto':
if not pick['spe']:
continue
@@ -397,22 +418,34 @@ class Array_map(QtWidgets.QWidget):
print('Cannot display pick for station {}. Reason: {}'.format(station_name, e))
return picks, uncertainties
def get_picks_rel(picks):
def get_picks_rel(picks, func=min):
picks_rel = {}
picks_utc = []
for pick in picks.values():
if type(pick) is obspy.core.utcdatetime.UTCDateTime:
picks_utc.append(pick)
if type(pick) is UTCDateTime:
picks_utc.append(pick.timestamp)
if picks_utc:
self._earliest_picktime = min(picks_utc)
self._reference_picktime = UTCDateTime(func(picks_utc))
for st_id, pick in picks.items():
if type(pick) is obspy.core.utcdatetime.UTCDateTime:
pick -= self._earliest_picktime
if type(pick) is UTCDateTime:
pick -= self._reference_picktime
picks_rel[st_id] = pick
return picks_rel
def get_picks_rel_mean_corr(picks):
return get_picks_rel(picks, func=np.nanmean)
self.picks, self.uncertainties = get_picks(self.stations_dict)
self.picks_rel = get_picks_rel(self.picks)
self.picks_rel_mean_corrected = get_picks_rel_mean_corr(self.picks)
def toggle_subtract_mean(self):
if self.subtract_mean_cb.isChecked():
cmap = 'seismic'
else:
cmap = 'viridis'
self.cmaps_box.setCurrentIndex(self.cmaps_box.findText(cmap))
self._refresh_drawings()
def init_lat_lon_dimensions(self):
# init minimum and maximum lon and lat dimensions
@@ -443,11 +476,12 @@ class Array_map(QtWidgets.QWidget):
return stations, latitudes, longitudes
def get_picks_lat_lon(self):
picks_rel = self.picks_rel_mean_corrected if self.subtract_mean_cb.isChecked() else self.picks_rel
picks = []
uncertainties = []
latitudes = []
longitudes = []
for st_id, pick in self.picks_rel.items():
for st_id, pick in picks_rel.items():
picks.append(pick)
uncertainties.append(self.uncertainties.get(st_id))
latitudes.append(self.stations_dict[st_id]['latitude'])
@@ -459,22 +493,31 @@ class Array_map(QtWidgets.QWidget):
stat_dict = self.stations_dict['{}.{}'.format(network, station)]
lat = stat_dict['latitude']
lon = stat_dict['longitude']
self.highlighted_stations.append(self.canvas.axes.scatter(lon, lat, s=self.pointsize, edgecolors=color,
self.highlighted_stations.append(self.ax.scatter(lon, lat, s=self.pointsize, edgecolors=color,
facecolors='none', zorder=12,
transform=ccrs.PlateCarree(), label='deleted'))
def openPickDlg(self, ind):
data = self._parent.get_data().getWFData()
try:
wfdata = self._parent.get_data().getWFData()
except AttributeError:
QtWidgets.QMessageBox.warning(
self, "PyLoT Warning",
"No waveform data found. Check if they were already loaded in Waveform plot tab."
)
return
wfdata_comp = self._parent.get_data().getAltWFdata()
for index in ind:
network, station = self._station_onpick_ids[index].split('.')[:2]
pyl_mw = self._parent
try:
data = data.select(station=station)
if not data:
wfdata = wfdata.select(station=station)
wfdata_comp = wfdata_comp.select(station=station)
if not wfdata:
self._warn('No data for station {}'.format(station))
return
pickDlg = PickDlg(self._parent, parameter=self.parameter,
data=data, network=network, station=station,
data=wfdata.copy(), data_compare=wfdata_comp.copy(), network=network, station=station,
picks=self._parent.get_current_event().getPick(station),
autopicks=self._parent.get_current_event().getAutopick(station),
filteroptions=self._parent.filteroptions, metadata=self.metadata,
@@ -505,20 +548,27 @@ class Array_map(QtWidgets.QWidget):
print(message, e)
print(traceback.format_exc())
def draw_contour_filled(self, nlevel=50):
levels = np.linspace(self.get_min_from_picks(), self.get_max_from_picks(), nlevel)
def draw_contour_filled(self, nlevel=51):
if self.subtract_mean_cb.isChecked():
abs_max = self.get_residuals_absmax()
levels = np.linspace(-abs_max, abs_max, nlevel)
else:
levels = np.linspace(min(self.picks_rel.values()), max(self.picks_rel.values()), nlevel)
self.contourf = self.canvas.axes.contourf(self.longrid, self.latgrid, self.picksgrid_active, levels,
self.contourf = self.ax.contourf(self.longrid, self.latgrid, self.picksgrid_active, levels,
linewidths=self.linewidth * 5, transform=ccrs.PlateCarree(),
alpha=0.4, zorder=8, cmap=self.get_colormap())
def get_residuals_absmax(self):
return np.max(np.absolute(list(self.picks_rel_mean_corrected.values())))
def get_colormap(self):
return plt.get_cmap(self.cmaps_box.currentText())
def scatter_all_stations(self):
stations, lats, lons = self.get_st_lat_lon_for_plot()
self.sc = self.canvas.axes.scatter(lons, lats, s=self.pointsize * 3, facecolor='none', marker='.',
self.sc = self.ax.scatter(lons, lats, s=self.pointsize * 3, facecolor='none', marker='.',
zorder=10, picker=True, edgecolor='0.5', label='Not Picked',
transform=ccrs.PlateCarree())
@@ -526,7 +576,7 @@ class Array_map(QtWidgets.QWidget):
self._station_onpick_ids = stations
if self.eventLoc:
lats, lons = self.eventLoc
self.sc_event = self.canvas.axes.scatter(lons, lats, s=5 * self.pointsize, facecolor='red', zorder=11,
self.sc_event = self.ax.scatter(lons, lats, s=5 * self.pointsize, facecolor='red', zorder=11,
label='Event (might be outside map region)', marker='*',
edgecolors='black',
transform=ccrs.PlateCarree())
@@ -542,7 +592,12 @@ class Array_map(QtWidgets.QWidget):
for uncertainty in uncertainties])
cmap = self.get_colormap()
self.sc_picked = self.canvas.axes.scatter(lons, lats, s=sizes, edgecolors='white', cmap=cmap,
vmin = vmax = None
if self.subtract_mean_cb.isChecked():
vmin, vmax = -self.get_residuals_absmax(), self.get_residuals_absmax()
self.sc_picked = self.ax.scatter(lons, lats, s=sizes, edgecolors='white', cmap=cmap, vmin=vmin, vmax=vmax,
c=picks, zorder=11, label='Picked', transform=ccrs.PlateCarree())
def annotate_ax(self):
@@ -561,20 +616,20 @@ class Array_map(QtWidgets.QWidget):
if st in self.marked_stations:
color = 'red'
self.annotations.append(
self.canvas.axes.annotate(' %s' % st, xy=(x + 0.003, y + 0.003), fontsize=self.pointsize / 4.,
self.ax.annotate(f'{st}', xy=(x + 0.003, y + 0.003), fontsize=self.pointsize / 4.,
fontweight='semibold', color=color, alpha=0.8,
transform=ccrs.PlateCarree(), zorder=14,
path_effects=[PathEffects.withStroke(
linewidth=self.pointsize / 15., foreground='k')]))
self.legend = self.canvas.axes.legend(loc=1, framealpha=1)
self.legend = self.ax.legend(loc=1, framealpha=1)
self.legend.set_zorder(100)
self.legend.get_frame().set_facecolor((1, 1, 1, 0.95))
def add_cbar(self, label):
self.cbax_bg = inset_axes(self.canvas.axes, width="6%", height="75%", loc=5)
cbax = inset_axes(self.canvas.axes, width='2%', height='70%', loc=5)
cbar = self.canvas.axes.figure.colorbar(self.sc_picked, cax=cbax)
self.cbax_bg = inset_axes(self.ax, width="6%", height="75%", loc=5)
cbax = inset_axes(self.ax, width='2%', height='70%', loc=5)
cbar = self.ax.figure.colorbar(self.sc_picked, cax=cbax)
cbar.set_label(label)
cbax.yaxis.tick_left()
cbax.yaxis.set_label_position('left')
@@ -619,7 +674,9 @@ class Array_map(QtWidgets.QWidget):
if picks_available:
self.scatter_picked_stations()
if hasattr(self, 'sc_picked'):
self.cbar = self.add_cbar(label='Time relative to first onset ({}) [s]'.format(self._earliest_picktime))
self.cbar = self.add_cbar(
label='Time relative to reference onset ({}) [s]'.format(self._reference_picktime)
)
self.comboBox_phase.setEnabled(True)
else:
self.comboBox_phase.setEnabled(False)
+22 -6
View File
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import glob
import logging
import os
import sys
@@ -26,6 +27,10 @@ class Metadata(object):
# saves which metadata files are from obspy dmt
self.obspy_dmt_invs = []
if inventory:
# make sure that no accidental backslashes mess up the path
if isinstance(inventory, str):
inventory = inventory.replace('\\', '/')
inventory = os.path.abspath(inventory)
if os.path.isdir(inventory):
self.add_inventory(inventory)
if os.path.isfile(inventory):
@@ -54,6 +59,8 @@ class Metadata(object):
:type path_to_inventory: str
:return: None
"""
path_to_inventory = path_to_inventory.replace('\\', '/')
path_to_inventory = os.path.abspath(path_to_inventory)
assert (os.path.isdir(path_to_inventory)), '{} is no directory'.format(path_to_inventory)
if path_to_inventory not in self.inventories:
self.inventories.append(path_to_inventory)
@@ -189,7 +196,11 @@ class Metadata(object):
metadata = self.get_metadata(seed_id, time)
if not metadata:
return
try:
return metadata['data'].get_coordinates(seed_id, time)
# no specific exception defined in obspy inventory
except Exception as e:
logging.warning(f'Could not get metadata for {seed_id}')
def get_all_coordinates(self):
def stat_info_from_parser(parser):
@@ -209,9 +220,9 @@ class Metadata(object):
network_name = network.code
if not station_name in self.stations_dict.keys():
st_id = '{}.{}'.format(network_name, station_name)
self.stations_dict[st_id] = {'latitude': station[0].latitude,
'longitude': station[0].longitude,
'elevation': station[0].elevation}
self.stations_dict[st_id] = {'latitude': station.latitude,
'longitude': station.longitude,
'elevation': station.elevation}
read_stat = {'xml': stat_info_from_inventory,
'dless': stat_info_from_parser}
@@ -257,9 +268,6 @@ class Metadata(object):
if not fnames:
# search for station name in filename
fnames = glob.glob(os.path.join(path_to_inventory, '*' + station + '*'))
if not fnames:
# search for network name in filename
fnames = glob.glob(os.path.join(path_to_inventory, '*' + network + '*'))
if not fnames:
if self.verbosity:
print('Could not find filenames matching station name, network name or seed id')
@@ -279,6 +287,7 @@ class Metadata(object):
self.seed_ids[station_seed_id] = fname
return True
except Exception as e:
logging.warning(e)
continue
print('Could not find metadata for station_seed_id {} in path {}'.format(station_seed_id, path_to_inventory))
@@ -643,6 +652,8 @@ def restitute_data(data, metadata, unit='VEL', force=False, ncores=0):
"""
# data = remove_underscores(data)
if not data:
return
# loop over traces
input_tuples = []
@@ -650,6 +661,11 @@ def restitute_data(data, metadata, unit='VEL', force=False, ncores=0):
input_tuples.append((tr, metadata, unit, force))
data.remove(tr)
if ncores == 0:
result = []
for input_tuple in input_tuples:
result.append(restitute_trace(input_tuple))
else:
pool = gen_Pool(ncores)
result = pool.imap_unordered(restitute_trace, input_tuples)
pool.close()
+1 -3
View File
@@ -26,9 +26,7 @@ elif system_name == "Windows":
# suffix for phase name if not phase identified by last letter (P, p, etc.)
ALTSUFFIX = ['diff', 'n', 'g', '1', '2', '3']
FILTERDEFAULTS = readDefaultFilterInformation(os.path.join(os.path.expanduser('~'),
'.pylot',
'pylot.in'))
FILTERDEFAULTS = readDefaultFilterInformation()
TIMEERROR_DEFAULTS = os.path.join(os.path.expanduser('~'),
'.pylot',
-3
View File
@@ -22,14 +22,11 @@ class Event(ObsPyEvent):
:param path: path to event directory
:type path: str
"""
# TODO: remove rootpath and database
self.pylot_id = path.split('/')[-1]
# initialize super class
super(Event, self).__init__(resource_id=ResourceIdentifier('smi:local/' + self.pylot_id))
self.path = path
self.database = path.split('/')[-2]
self.datapath = os.path.split(path)[0] # path.split('/')[-3]
self.rootpath = '/' + os.path.join(*path.split('/')[:-3])
self.pylot_autopicks = {}
self.pylot_picks = {}
self.notes = ''
+1 -1
View File
@@ -42,7 +42,7 @@ def main(project_file_path, manual=False, auto=True, file_format='png', f_ext=''
for item in input_list:
array_map_worker(item)
else:
pool = multiprocessing.Pool(ncores)
pool = multiprocessing.Pool(ncores, maxtasksperchild=1000)
pool.map(array_map_worker, input_list)
pool.close()
pool.join()
+11 -9
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from functools import lru_cache
try:
import pyqtgraph as pg
@@ -25,14 +26,14 @@ def pick_linestyle_pg(picktype, key):
:return: Qt line style parameters
:rtype:
"""
linestyles_manu = {'mpp': (QtCore.Qt.SolidLine, 2.),
'epp': (QtCore.Qt.DashLine, 1.),
'lpp': (QtCore.Qt.DashLine, 1.),
'spe': (QtCore.Qt.DashLine, 1.)}
linestyles_auto = {'mpp': (QtCore.Qt.DotLine, 2.),
'epp': (QtCore.Qt.DashDotLine, 1.),
'lpp': (QtCore.Qt.DashDotLine, 1.),
'spe': (QtCore.Qt.DashDotLine, 1.)}
linestyles_manu = {'mpp': (QtCore.Qt.SolidLine, 2),
'epp': (QtCore.Qt.DashLine, 1),
'lpp': (QtCore.Qt.DashLine, 1),
'spe': (QtCore.Qt.DashLine, 1)}
linestyles_auto = {'mpp': (QtCore.Qt.DotLine, 2),
'epp': (QtCore.Qt.DashDotLine, 1),
'lpp': (QtCore.Qt.DashDotLine, 1),
'spe': (QtCore.Qt.DashDotLine, 1)}
linestyles = {'manual': linestyles_manu,
'auto': linestyles_auto}
return linestyles[picktype][key]
@@ -56,7 +57,7 @@ def which(program, parameter):
nllocpath = ":" + parameter.get('nllocbin')
os.environ['PATH'] += nllocpath
except Exception as e:
print(e.message)
print(e)
def is_exe(fpath):
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
@@ -80,6 +81,7 @@ def which(program, parameter):
return None
@lru_cache(maxsize=128)
def make_pen(picktype, phase, key, quality):
"""
Make PyQtGraph.QPen
+11 -4
View File
@@ -22,9 +22,11 @@ class Thread(QThread):
self.abortButton = abortButton
self.finished.connect(self.hideProgressbar)
self.showProgressbar()
self.old_stdout = None
def run(self):
if self.redirect_stdout:
self.old_stdout = sys.stdout
sys.stdout = self
try:
if self.arg is not None:
@@ -39,7 +41,8 @@ class Thread(QThread):
exctype, value = sys.exc_info()[:2]
self._executedErrorInfo = '{} {} {}'. \
format(exctype, value, traceback.format_exc())
sys.stdout = sys.__stdout__
if self.redirect_stdout:
sys.stdout = self.old_stdout
def showProgressbar(self):
if self.progressText:
@@ -96,10 +99,12 @@ class Worker(QRunnable):
self.progressText = progressText
self.pb_widget = pb_widget
self.redirect_stdout = redirect_stdout
self.old_stdout = None
@Slot()
def run(self):
if self.redirect_stdout:
self.old_stdout = sys.stdout
sys.stdout = self
try:
@@ -112,7 +117,7 @@ class Worker(QRunnable):
self.signals.result.emit(result)
finally:
self.signals.finished.emit('Done')
sys.stdout = sys.__stdout__
sys.stdout = self.old_stdout
def write(self, text):
self.signals.message.emit(text)
@@ -144,16 +149,18 @@ class MultiThread(QThread):
self.progressText = progressText
self.pb_widget = pb_widget
self.redirect_stdout = redirect_stdout
self.old_stdout = None
self.finished.connect(self.hideProgressbar)
self.showProgressbar()
def run(self):
if self.redirect_stdout:
self.old_stdout = sys.stdout
sys.stdout = self
try:
if not self.ncores:
self.ncores = multiprocessing.cpu_count()
pool = multiprocessing.Pool(self.ncores)
pool = multiprocessing.Pool(self.ncores, maxtasksperchild=1000)
self.data = pool.map_async(self.func, self.args, callback=self.emitDone)
# self.data = pool.apply_async(self.func, self.shotlist, callback=self.emitDone) #emit each time returned
pool.close()
@@ -164,7 +171,7 @@ class MultiThread(QThread):
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
print('Exception: {}, file: {}, line: {}'.format(exc_type, fname, exc_tb.tb_lineno))
sys.stdout = sys.__stdout__
sys.stdout = self.old_stdout
def showProgressbar(self):
if self.progressText:
+270 -171
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import glob
import hashlib
import logging
import os
import platform
import re
import subprocess
import warnings
from typing import Literal, Tuple, Type
from functools import lru_cache
import numpy as np
from obspy import UTCDateTime, read
@@ -18,6 +21,10 @@ from pylot.core.io.inputs import PylotParameter, FilterOptions
from pylot.core.util.obspyDMT_interface import check_obspydmt_eventfolder
from pylot.styles import style_settings
Rgba: Type[tuple] = Tuple[int, int, int, int]
Mplrgba: Type[tuple] = Tuple[float, float, float, float]
Mplrgbastr: Type[tuple] = Tuple[str, str, str, str]
def _pickle_method(m):
if m.im_self is None:
@@ -37,15 +44,13 @@ def getAutoFilteroptions(phase, parameter):
return filteroptions
def readDefaultFilterInformation(fname):
def readDefaultFilterInformation():
"""
Read default filter information from pylot.in file
:param fname: path to pylot.in file
:type fname: str
:return: dictionary containing the defailt filter information
:rtype: dict
"""
pparam = PylotParameter(fname)
pparam = PylotParameter()
return readFilterInformation(pparam)
@@ -82,25 +87,6 @@ def fit_curve(x, y):
return splev, splrep(x, y)
def getindexbounds(f, eta):
"""
Get indices of values closest below and above maximum value in an array
:param f: array
:type f: `~numpy.ndarray`
:param eta: look for value in array that is closes to max_value * eta
:type eta: float
:return: tuple containing index of max value, index of value closest below max value,
index of value closest above max value
:rtype: (int, int, int)
"""
mi = f.argmax() # get indices of max values
m = max(f) # get maximum value
b = m * eta #
l = find_nearest(f[:mi], b) # find closest value below max value
u = find_nearest(f[mi:], b) + mi # find closest value above max value
return mi, l, u
def gen_Pool(ncores=0):
"""
Generate mulitprocessing pool object utilizing ncores amount of cores
@@ -120,7 +106,7 @@ def gen_Pool(ncores=0):
print('gen_Pool: Generated multiprocessing Pool with {} cores\n'.format(ncores))
pool = multiprocessing.Pool(ncores)
pool = multiprocessing.Pool(ncores, maxtasksperchild=100)
return pool
@@ -166,11 +152,11 @@ def clims(lim1, lim2):
"""
takes two pairs of limits and returns one pair of common limts
:param lim1: limit 1
:type lim1: int
:type lim1: List[int]
:param lim2: limit 2
:type lim2: int
:type lim2: List[int]
:return: new upper and lower limit common to both given limits
:rtype: [int, int]
:rtype: List[int]
>>> clims([0, 4], [1, 3])
[0, 4]
@@ -302,7 +288,7 @@ def fnConstructor(s):
if type(s) is str:
s = s.split(':')[-1]
else:
s = getHash(UTCDateTime())
s = get_hash(UTCDateTime())
badchars = re.compile(r'[^A-Za-z0-9_. ]+|^\.|\.$|^ | $|^$')
badsuffix = re.compile(r'(aux|com[1-9]|con|lpt[1-9]|prn)(\.|$)')
@@ -314,32 +300,75 @@ def fnConstructor(s):
return fn
def get_None(value):
def get_none(value):
"""
Convert "None" to None
:param value:
:type value: str, bool
:type value: str, NoneType
:return:
:rtype: bool
:rtype: type(value) or NoneType
>>> st = read()
>>> print(get_none(st))
3 Trace(s) in Stream:
BW.RJOB..EHZ | 2009-08-24T00:20:03.000000Z - 2009-08-24T00:20:32.990000Z | 100.0 Hz, 3000 samples
BW.RJOB..EHN | 2009-08-24T00:20:03.000000Z - 2009-08-24T00:20:32.990000Z | 100.0 Hz, 3000 samples
BW.RJOB..EHE | 2009-08-24T00:20:03.000000Z - 2009-08-24T00:20:32.990000Z | 100.0 Hz, 3000 samples
>>> get_none('Stream')
'Stream'
>>> get_none(0)
0
>>> get_none(0.)
0.0
>>> print(get_none('None'))
None
>>> print(get_none(None))
None
"""
if value == 'None':
if value is None or (type(value) is str and value == 'None'):
return None
else:
return value
def get_Bool(value):
def get_bool(value):
"""
Convert string representations of bools to their true boolean value
Convert string representations of bools to their true boolean value. Return value if it cannot be identified as bool.
:param value:
:type value: str, bool
:type value: str, bool, int, float
:return: true boolean value
:rtype: bool
>>> get_bool(True)
True
>>> get_bool(False)
False
>>> get_bool(0)
False
>>> get_bool(0.)
False
>>> get_bool(0.1)
True
>>> get_bool(2)
True
>>> get_bool(-1)
False
>>> get_bool(-0.3)
False
>>> get_bool(None)
None
"""
if value in ['True', 'true']:
if type(value) is bool:
return value
elif value in ['True', 'true']:
return True
elif value in ['False', 'false']:
return False
elif isinstance(value, float) or isinstance(value, int):
if value > 0. or value > 0:
return True
else:
return False
else:
return value
@@ -353,8 +382,8 @@ def four_digits(year):
:return: four digit year correspondent
:rtype: int
>>> four_digits(20)
1920
>>> four_digits(75)
1975
>>> four_digits(16)
2016
>>> four_digits(00)
@@ -436,36 +465,53 @@ def backtransformFilterString(st):
return st
def getHash(time):
def get_hash(time):
"""
takes a time object and returns the corresponding SHA1 hash of the formatted date string
:param time: time object for which a hash should be calculated
:type time: `~obspy.core.utcdatetime.UTCDateTime`
:return: SHA1 hash
:rtype: str
>>> time = UTCDateTime(0)
>>> get_hash(time)
'7627cce3b1b58dd21b005dac008b34d18317dd15'
>>> get_hash(0)
Traceback (most recent call last):
...
AssertionError: 'time' is not an ObsPy UTCDateTime object
"""
assert isinstance(time, UTCDateTime), '\'time\' is not an ObsPy UTCDateTime object'
hg = hashlib.sha1()
hg.update(time.strftime('%Y-%m-%d %H:%M:%S.%f'))
hg.update(time.strftime('%Y-%m-%d %H:%M:%S.%f').encode('utf-8'))
return hg.hexdigest()
def getLogin():
def get_login():
"""
returns the actual user's login ID
:return: login ID
returns the actual user's name
:return: login name
:rtype: str
"""
import getpass
return getpass.getuser()
def getOwner(fn):
def get_owner(fn):
"""
takes a filename and return the login ID of the actual owner of the file
:param fn: filename of the file tested
:type fn: str
:return: login ID of the file's owner
:rtype: str
>>> import tempfile
>>> with tempfile.NamedTemporaryFile() as tmpfile:
... tmpfile.write(b'') and True
... tmpfile.flush()
... get_owner(tmpfile.name) == os.path.expanduser('~').split('/')[-1]
0
True
"""
system_name = platform.system()
if system_name in ["Linux", "Darwin"]:
@@ -511,6 +557,11 @@ def is_executable(fn):
:param fn: path to the file to be tested
:return: True or False
:rtype: bool
>>> is_executable('/bin/ls')
True
>>> is_executable('/var/log/system.log')
False
"""
return os.path.isfile(fn) and os.access(fn, os.X_OK)
@@ -537,24 +588,36 @@ def isSorted(iterable):
>>> isSorted([2,3,1,4])
False
"""
assert isIterable(iterable), 'object is not iterable; object: {' \
'}'.format(iterable)
assert is_iterable(iterable), "object is not iterable; object: {}".format(iterable)
if type(iterable) is str:
iterable = [s for s in iterable]
return sorted(iterable) == iterable
def isIterable(obj):
def is_iterable(obj):
"""
takes a python object and returns True is the object is iterable and
False otherwise
:param obj: a python object
:type obj: object
:type obj: obj
:return: True of False
:rtype: bool
>>> is_iterable(1)
False
>>> is_iterable(True)
False
>>> is_iterable(0.)
False
>>> is_iterable((0,1,3,4))
True
>>> is_iterable([1])
True
>>> is_iterable('a')
True
"""
try:
iterator = iter(obj)
iter(obj)
except TypeError as te:
return False
return True
@@ -563,13 +626,19 @@ def isIterable(obj):
def key_for_set_value(d):
"""
takes a dictionary and returns the first key for which's value the
boolean is True
boolean representation is True
:param d: dictionary containing values
:type d: dict
:return: key to the first non-False value found; None if no value's
boolean equals True
:rtype:
:rtype: bool or NoneType
>>> key_for_set_value({'one': 0, 'two': 1})
'two'
>>> print(key_for_set_value({1: 0, 2: False}))
None
"""
assert type(d) is dict, "Function only defined for inputs of type 'dict'."
r = None
for k, v in d.items():
if v:
@@ -577,32 +646,53 @@ def key_for_set_value(d):
return r
def prepTimeAxis(stime, trace, verbosity=0):
def prep_time_axis(offset, trace, verbosity=0):
"""
takes a starttime and a trace object and returns a valid time axis for
takes an offset and a trace object and returns a valid time axis for
plotting
:param stime: start time of the actual seismogram as UTCDateTime
:type stime: `~obspy.core.utcdatetime.UTCDateTime`
:param offset: offset of the actual seismogram on plotting axis
:type offset: float or int
:param trace: seismic trace object
:type trace: `~obspy.core.trace.Trace`
:param verbosity: if != 0, debug output will be written to console
:type verbosity: int
:return: valid numpy array with time stamps for plotting
:rtype: `~numpy.ndarray`
>>> tr = read()[0]
>>> prep_time_axis(0., tr)
array([0.00000000e+00, 1.00033344e-02, 2.00066689e-02, ...,
2.99799933e+01, 2.99899967e+01, 3.00000000e+01])
>>> prep_time_axis(22.5, tr)
array([22.5 , 22.51000333, 22.52000667, ..., 52.47999333,
52.48999667, 52.5 ])
>>> prep_time_axis(tr.stats.starttime, tr)
Traceback (most recent call last):
...
AssertionError: 'offset' is not of type 'float' or 'int'; type: <class 'obspy.core.utcdatetime.UTCDateTime'>
>>> tr.stats.npts -= 1
>>> prep_time_axis(0, tr)
array([0.00000000e+00, 1.00033356e-02, 2.00066711e-02, ...,
2.99699933e+01, 2.99799967e+01, 2.99900000e+01])
>>> tr.stats.npts += 2
>>> prep_time_axis(0, tr)
array([0.00000000e+00, 1.00033333e-02, 2.00066667e-02, ...,
2.99899933e+01, 2.99999967e+01, 3.00100000e+01])
"""
assert isinstance(offset, (float, int)), "'offset' is not of type 'float' or 'int'; type: {}".format(type(offset))
nsamp = trace.stats.npts
srate = trace.stats.sampling_rate
tincr = trace.stats.delta
etime = stime + nsamp / srate
time_ax = np.linspace(stime, etime, nsamp)
etime = offset + nsamp / srate
time_ax = np.linspace(offset, etime, nsamp)
if len(time_ax) < nsamp:
if verbosity:
print('elongate time axes by one datum')
time_ax = np.arange(stime, etime + tincr, tincr)
time_ax = np.arange(offset, etime + tincr, tincr)
elif len(time_ax) > nsamp:
if verbosity:
print('shorten time axes by one datum')
time_ax = np.arange(stime, etime - tincr, tincr)
time_ax = np.arange(offset, etime - tincr, tincr)
if len(time_ax) != nsamp:
print('Station {0}, {1} samples of data \n '
'{2} length of time vector \n'
@@ -618,13 +708,13 @@ def find_horizontals(data):
:param data: waveform data
:type data: `obspy.core.stream.Stream`
:return: components list
:rtype: list
:rtype: List(str)
..example::
>>> st = read()
>>> find_horizontals(st)
[u'N', u'E']
['N', 'E']
"""
rval = []
for tr in data:
@@ -635,7 +725,7 @@ def find_horizontals(data):
return rval
def pick_color(picktype, phase, quality=0):
def pick_color(picktype: Literal['manual', 'automatic'], phase: Literal['P', 'S'], quality: int = 0) -> Rgba:
"""
Create pick color by modifying the base color by the quality.
@@ -648,7 +738,7 @@ def pick_color(picktype, phase, quality=0):
:param quality: quality of pick. Decides the new intensity of the modifier color
:type quality: int
:return: tuple containing modified rgba color values
:rtype: (int, int, int, int)
:rtype: Rgba
"""
min_quality = 3
bpc = base_phase_colors(picktype, phase) # returns dict like {'modifier': 'g', 'rgba': (0, 0, 255, 255)}
@@ -704,17 +794,17 @@ def pick_linestyle_plt(picktype, key):
return linestyles[picktype][key]
def modify_rgba(rgba, modifier, intensity):
def modify_rgba(rgba: Rgba, modifier: Literal['r', 'g', 'b'], intensity: float) -> Rgba:
"""
Modify rgba color by adding the given intensity to the modifier color
:param rgba: tuple containing rgba values
:type rgba: (int, int, int, int)
:param modifier: which color should be modified, eg. 'r', 'g', 'b'
:type modifier: str
:type rgba: Rgba
:param modifier: which color should be modified; options: 'r', 'g', 'b'
:type modifier: Literal['r', 'g', 'b']
:param intensity: intensity to be added to selected color
:type intensity: float
:return: tuple containing rgba values
:rtype: (int, int, int, int)
:rtype: Rgba
"""
rgba = list(rgba)
index = {'r': 0,
@@ -748,18 +838,20 @@ def transform_colors_mpl_str(colors, no_alpha=False):
Transforms rgba color values to a matplotlib string of color values with a range of [0, 1]
:param colors: tuple of rgba color values ranging from [0, 255]
:type colors: (float, float, float, float)
:param no_alpha: Wether to return a alpha value in the matplotlib color string
:param no_alpha: Whether to return an alpha value in the matplotlib color string
:type no_alpha: bool
:return: String containing r, g, b values and alpha value if no_alpha is False (default)
:rtype: str
>>> transform_colors_mpl_str((255., 255., 255., 255.), True)
'(1.0, 1.0, 1.0)'
>>> transform_colors_mpl_str((255., 255., 255., 255.))
'(1.0, 1.0, 1.0, 1.0)'
"""
colors = list(colors)
colors_mpl = tuple([color / 255. for color in colors])
if no_alpha:
colors_mpl = '({}, {}, {})'.format(*colors_mpl)
return '({}, {}, {})'.format(*transform_colors_mpl(colors))
else:
colors_mpl = '({}, {}, {}, {})'.format(*colors_mpl)
return colors_mpl
return '({}, {}, {}, {})'.format(*transform_colors_mpl(colors))
def transform_colors_mpl(colors):
@@ -769,27 +861,16 @@ def transform_colors_mpl(colors):
:type colors: (float, float, float, float)
:return: tuple of rgba color values ranging from [0, 1]
:rtype: (float, float, float, float)
>>> transform_colors_mpl((127.5, 0., 63.75, 255.))
(0.5, 0.0, 0.25, 1.0)
>>> transform_colors_mpl(())
"""
colors = list(colors)
colors_mpl = tuple([color / 255. for color in colors])
return colors_mpl
def remove_underscores(data):
"""
takes a `obspy.core.stream.Stream` object and removes all underscores
from station names
:param data: stream of seismic data
:type data: `~obspy.core.stream.Stream`
:return: data stream
:rtype: `~obspy.core.stream.Stream`
"""
# for tr in data:
# # remove underscores
# tr.stats.station = tr.stats.station.strip('_')
return data
def trim_station_components(data, trim_start=True, trim_end=True):
"""
cut a stream so only the part common to all three traces is kept to avoid dealing with offsets
@@ -818,19 +899,6 @@ def trim_station_components(data, trim_start=True, trim_end=True):
return data
def merge_stream(stream):
gaps = stream.get_gaps()
if gaps:
# list of merged stations (seed_ids)
merged = ['{}.{}.{}.{}'.format(*gap[:4]) for gap in gaps]
stream.merge(method=1)
print('Merged the following stations because of gaps:')
for merged_station in merged:
print(merged_station)
return stream, gaps
def check4gapsAndRemove(data):
"""
check for gaps in Stream and remove them
@@ -851,12 +919,12 @@ def check4gapsAndRemove(data):
return data
def check4gapsAndMerge(data):
def check_for_gaps_and_merge(data):
"""
check for gaps in Stream and merge if gaps are found
:param data: stream of seismic data
:type data: `~obspy.core.stream.Stream`
:return: data stream
:return: data stream, gaps returned from obspy get_gaps
:rtype: `~obspy.core.stream.Stream`
"""
gaps = data.get_gaps()
@@ -867,7 +935,7 @@ def check4gapsAndMerge(data):
for merged_station in merged:
print(merged_station)
return data
return data, gaps
def check4doubled(data):
@@ -897,13 +965,53 @@ def check4doubled(data):
return data
def check_for_nan(data, nan_value=0.):
"""
Replace all NaNs in data with nan_value (in place)
:param data: stream of seismic data
:type data: `~obspy.core.stream.Stream`
:param nan_value: value which all NaNs are set to
:type nan_value: float, int
:return: None
"""
if not data:
return
for trace in data:
np.nan_to_num(trace.data, copy=False, nan=nan_value)
def get_pylot_eventfile_with_extension(event, fext):
if hasattr(event, 'path'):
eventpath = event.path
else:
logging.warning('No attribute path found for event.')
return
eventname = event.pylot_id #path.split('/')[-1] # or event.pylot_id
filename = os.path.join(eventpath, 'PyLoT_' + eventname + fext)
if os.path.isfile(filename):
return filename
def get_possible_pylot_eventfile_extensions(event, fext):
if hasattr(event, 'path'):
eventpath = event.path
else:
logging.warning('No attribute path found for event.')
return []
eventname = event.pylot_id
filename = os.path.join(eventpath, 'PyLoT_' + eventname + fext)
filenames = glob.glob(filename)
extensions = [os.path.split(path)[-1].split('PyLoT_' + eventname)[-1] for path in filenames]
return extensions
def get_stations(data):
"""
Get list of all station names in data stream
Get list of all station names in data-stream
:param data: stream containing seismic traces
:type data: `~obspy.core.stream.Stream`
:return: list of all station names in data, no duplicates
:rtype: list of str
:rtype: List(str)
"""
stations = []
for tr in data:
@@ -930,66 +1038,88 @@ def check4rotated(data, metadata=None, verbosity=1):
:rtype: `~obspy.core.stream.Stream`
"""
def rotate_components(wfstream, metadata=None):
def rotation_required(trace_ids):
"""
Derive if any rotation is required from the orientation code of the input.
:param trace_ids: string identifier of waveform data trace
:type trace_ids: List(str)
:return: boolean representing if rotation is necessary for any of the traces
:rtype: bool
"""
orientations = [trace_id[-1] for trace_id in trace_ids]
return any([orientation.isnumeric() for orientation in orientations])
def rotate_components(wfs_in, metadata=None):
"""
Rotate components if orientation code is numeric (= non traditional orientation).
Azimut and dip are fetched from metadata. To be rotated, traces of a station have to be cut to the same length.
Returns unrotated traces of no metadata is provided
:param wfstream: stream containing seismic traces of a station
:type wfstream: `~obspy.core.stream.Stream`
:param wfs_in: stream containing seismic traces of a station
:type wfs_in: `~obspy.core.stream.Stream`
:param metadata: tuple containing metadata type string and metadata parser object
:type metadata: (str, `~obspy.io.xseed.parser.Parser`)
:return: stream object with traditionally oriented traces (ZNE)
:rtype: `~obspy.core.stream.Stream`
"""
if len(wfs_in) < 3:
print(f"Stream {wfs_in=}, has not enough components to rotate.")
return wfs_in
# check if any traces in this station need to be rotated
trace_ids = [trace.id for trace in wfstream]
orientations = [trace_id[-1] for trace_id in trace_ids]
rotation_required = [orientation.isnumeric() for orientation in orientations]
if any(rotation_required):
t_start = full_range(wfstream)
trace_ids = [trace.id for trace in wfs_in]
if not rotation_required(trace_ids):
logging.debug(f"Stream does not need any rotation: Traces are {trace_ids=}")
return wfs_in
# check metadata quality
t_start = full_range(wfs_in)[0]
try:
azimuts = []
azimuths = []
dips = []
for tr_id in trace_ids:
azimuts.append(metadata.get_coordinates(tr_id, t_start)['azimuth'])
azimuths.append(metadata.get_coordinates(tr_id, t_start)['azimuth'])
dips.append(metadata.get_coordinates(tr_id, t_start)['dip'])
except (KeyError, TypeError) as e:
print('Failed to rotate trace {}, no azimuth or dip available in metadata'.format(tr_id))
return wfstream
if len(wfstream) < 3:
print('Failed to rotate Stream {}, not enough components available.'.format(wfstream))
return wfstream
except (KeyError, TypeError) as err:
logging.warning(f"Rotating not possible, not all azimuth and dip information "
f"available in metadata. Stream remains unchanged.")
logging.debug(f"Rotating not possible, {err=}, {type(err)=}")
return wfs_in
except Exception as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
# to rotate all traces must have same length, so trim them
wfstream = trim_station_components(wfstream, trim_start=True, trim_end=True)
wfs_out = trim_station_components(wfs_in, trim_start=True, trim_end=True)
try:
z, n, e = rotate2zne(wfstream[0], azimuts[0], dips[0],
wfstream[1], azimuts[1], dips[1],
wfstream[2], azimuts[2], dips[2])
z, n, e = rotate2zne(wfs_out[0], azimuths[0], dips[0],
wfs_out[1], azimuths[1], dips[1],
wfs_out[2], azimuths[2], dips[2])
print('check4rotated: rotated trace {} to ZNE'.format(trace_ids))
# replace old data with rotated data, change the channel code to ZNE
z_index = dips.index(min(
dips)) # get z-trace index, z has minimum dip of -90 (dip is measured from 0 to -90, with -90 being vertical)
wfstream[z_index].data = z
wfstream[z_index].stats.channel = wfstream[z_index].stats.channel[0:-1] + 'Z'
dips)) # get z-trace index, z has minimum dip of -90 (dip is measured from 0 to -90, with -90
# being vertical)
wfs_out[z_index].data = z
wfs_out[z_index].stats.channel = wfs_out[z_index].stats.channel[0:-1] + 'Z'
del trace_ids[z_index]
for trace_id in trace_ids:
coordinates = metadata.get_coordinates(trace_id, t_start)
dip, az = coordinates['dip'], coordinates['azimuth']
trace = wfstream.select(id=trace_id)[0]
if az > 315 or az <= 45 or az > 135 and az <= 225:
trace = wfs_out.select(id=trace_id)[0]
if az > 315 or az <= 45 or 135 < az <= 225:
trace.data = n
trace.stats.channel = trace.stats.channel[0:-1] + 'N'
elif az > 45 and az <= 135 or az > 225 and az <= 315:
elif 45 < az <= 135 or 225 < az <= 315:
trace.data = e
trace.stats.channel = trace.stats.channel[0:-1] + 'E'
except (ValueError) as e:
print(e)
return wfstream
except ValueError as err:
print(f"{err=} Rotation failed. Stream remains unchanged.")
return wfs_in
return wfstream
return wfs_out
if metadata is None:
if verbosity:
@@ -1003,38 +1133,6 @@ def check4rotated(data, metadata=None, verbosity=1):
return data
def scaleWFData(data, factor=None, components='all'):
"""
produce scaled waveforms from given waveform data and a scaling factor,
waveform may be selected by their components name
:param data: waveform data to be scaled
:type data: `~obspy.core.stream.Stream` object
:param factor: scaling factor
:type factor: float
:param components: components labels for the traces in data to be scaled by
the scaling factor (optional, default: 'all')
:type components: tuple
:return: scaled waveform data
:rtype: `~obspy.core.stream.Stream` object
"""
if components != 'all':
for comp in components:
if factor is None:
max_val = np.max(np.abs(data.select(component=comp)[0].data))
data.select(component=comp)[0].data /= 2 * max_val
else:
data.select(component=comp)[0].data /= 2 * factor
else:
for tr in data:
if factor is None:
max_val = float(np.max(np.abs(tr.data)))
tr.data /= 2 * max_val
else:
tr.data /= 2 * factor
return data
def runProgram(cmd, parameter=None):
"""
run an external program specified by cmd with parameters input returning the
@@ -1106,6 +1204,7 @@ def identifyPhase(phase):
return False
@lru_cache
def identifyPhaseID(phase):
"""
Returns phase id (capital P or S)
@@ -1169,7 +1268,7 @@ def correct_iplot(iplot):
try:
iplot = int(iplot)
except ValueError:
if get_Bool(iplot):
if get_bool(iplot):
iplot = 2
else:
iplot = 0
+627 -127
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
#
@@ -0,0 +1,101 @@
############################# correlation parameters #####################################
# min_corr_stacking: minimum correlation coefficient for building beam trace
# min_corr_export: minimum correlation coefficient for pick export
# min_stack: minimum number of stations for building beam trace
# t_before: correlation window before pick
# t_after: correlation window after pick#
# cc_maxlag: maximum shift for initial correlation
# cc_maxlag2: maximum shift for second (final) correlation (also for calculating pick uncertainty)
# initial_pick_outlier_threshold: (hopefully) threshold for excluding large outliers of initial (AIC) picks
# export_threshold: automatically exclude all onsets which deviate more than this threshold from corrected taup onsets
# min_picks_export: minimum number of correlated picks for export
# min_picks_autopylot: minimum number of reference auto picks to continue with event
# check_RMS: do RMS check to search for restitution errors (very experimental)
# use_taupy_onsets: use taupy onsets as reference picks instead of external picks
# station_list: use the following stations as reference for stacking
# use_stacked_trace: use existing stacked trace if found (spare re-computation)
# data_dir: obspyDMT data subdirectory (e.g. 'raw', 'processed')
# pickfile_extension: use quakeML files (PyLoT output) with the following extension, e.g. '_autopylot' for pickfiles
# such as 'PyLoT_20170501_141822_autopylot.xml'
# dt_stacking: time shift for stacking (e.g. [0, 250] for 0 and 250 seconds shift)
# filter_options: filter for first correlation (rough)
# filter_options_final: filter for second correlation (fine)
# filter_type: e.g. 'bandpass'
# sampfreq: sampling frequency of the data
logging: info
pick_phases: ['P', 'S']
# P-phase
P:
min_corr_stacking: 0.8
min_corr_export: 0.6
min_stack: 20
t_before: 30.
t_after: 50.
cc_maxlag: 50.
cc_maxlag2: 5.
initial_pick_outlier_threshold: 30.
export_threshold: 2.5
min_picks_export: 100
min_picks_autopylot: 50
check_RMS: True
use_taupy_onsets: False
station_list: ['HU.MORH', 'HU.TIH', 'OX.FUSE', 'OX.BAD']
use_stacked_trace: False
data_dir: 'processed'
pickfile_extension: '_autopylot'
dt_stacking: [250, 250]
# filter for first correlation (rough)
filter_options:
freqmax: 0.5
freqmin: 0.03
# filter for second correlation (fine)
filter_options_final:
freqmax: 0.5
freqmin: 0.03
filter_type: bandpass
sampfreq: 20.0
# ignore if autopylot fails to pick master-trace (not recommended if absolute onset times matter)
ignore_autopylot_fail_on_master: True
# S-phase
S:
min_corr_stacking: 0.7
min_corr_export: 0.6
min_stack: 20
t_before: 60.
t_after: 60.
cc_maxlag: 100.
cc_maxlag2: 25.
initial_pick_outlier_threshold: 30.
export_threshold: 5.0
min_picks_export: 200
min_picks_autopylot: 50
check_RMS: True
use_taupy_onsets: False
station_list: ['HU.MORH','HU.TIH', 'OX.FUSE', 'OX.BAD']
use_stacked_trace: False
data_dir: 'processed'
pickfile_extension: '_autopylot'
dt_stacking: [250, 250]
# filter for first correlation (rough)
filter_options:
freqmax: 0.1
freqmin: 0.01
# filter for second correlation (fine)
filter_options_final:
freqmax: 0.2
freqmin: 0.01
filter_type: bandpass
sampfreq: 20.0
# ignore if autopylot fails to pick master-trace (not recommended if absolute onset times matter)
ignore_autopylot_fail_on_master: True
File diff suppressed because it is too large Load Diff
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
#ulimit -s 8192
#ulimit -v $(ulimit -v | awk '{printf("%d",$1*0.95)}')
#ulimit -v
#655360
source /opt/anaconda3/etc/profile.d/conda.sh
conda activate pylot_311
NSLOTS=20
#qsub -l low -cwd -l "os=*stretch" -pe smp 40 submit_pick_corr_correction.sh
#$ -l low
#$ -l h_vmem=6G
#$ -cwd
#$ -pe smp 20
#$ -N corr_pick
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot_tools/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot/"
#export MKL_NUM_THREADS=${NSLOTS:=1}
#export NUMEXPR_NUM_THREADS=${NSLOTS:=1}
#export OMP_NUM_THREADS=${NSLOTS:=1}
#python pick_correlation_correction.py '/data/AlpArray_Data/dmt_database_mantle_M5.8-6.0' '/home/marcel/.pylot/pylot_alparray_mantle_corr_stack_0.03-0.5.in' -pd -n ${NSLOTS:=1} -istart 0 -istop 100
#python pick_correlation_correction.py '/data/AlpArray_Data/dmt_database_mantle_M5.8-6.0' '/home/marcel/.pylot/pylot_alparray_mantle_corr_stack_0.03-0.5.in' -pd -n ${NSLOTS:=1} -istart 100 -istop 200
#python pick_correlation_correction.py '/data/AlpArray_Data/dmt_database_mantle_M6.0-6.5' '/home/marcel/.pylot/pylot_alparray_mantle_corr_stack_0.03-0.5.in' -pd -n ${NSLOTS:=1} -istart 0 -istop 100
#python pick_correlation_correction.py '/data/AlpArray_Data/dmt_database_mantle_M5.8-6.0' '/home/marcel/.pylot/pylot_alparray_mantle_corr_stack_0.03-0.5.in' -pd -n ${NSLOTS:=1} -istart 100 -istop 200
#python pick_correlation_correction.py 'H:\sciebo\dmt_database' 'H:\Sciebo\dmt_database\pylot_alparray_mantle_corr_S_0.01-0.2.in' -pd -n 4 -t
#pylot_infile='/home/marcel/.pylot/pylot_alparray_syn_fwi_mk6_it3.in'
pylot_infile='/home/marcel/.pylot/pylot_adriaarray_corr_P_and_S.in'
# THIS SCRIPT SHOLD BE CALLED BY "submit_to_grid_engine.py" using the following line:
# use -pd for detailed plots in eventdir/correlation_XX_XX/figures
python pick_correlation_correction.py $1 $pylot_infile -n ${NSLOTS:=1} -istart $2 --params 'parameters_adriaarray.yaml' # -pd
#--event_blacklist eventlist.txt
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python
import subprocess
fnames = [
('/data/AdriaArray_Data/dmt_database_mantle_M5.0-5.4', 0),
('/data/AdriaArray_Data/dmt_database_mantle_M5.4-5.7', 0),
('/data/AdriaArray_Data/dmt_database_mantle_M5.7-6.0', 0),
('/data/AdriaArray_Data/dmt_database_mantle_M6.0-6.3', 0),
('/data/AdriaArray_Data/dmt_database_mantle_M6.3-10.0', 0),
# ('/data/AdriaArray_Data/dmt_database_ISC_mantle_M5.0-5.4', 0),
# ('/data/AdriaArray_Data/dmt_database_ISC_mantle_M5.4-5.7', 0),
# ('/data/AdriaArray_Data/dmt_database_ISC_mantle_M5.7-6.0', 0),
# ('/data/AdriaArray_Data/dmt_database_ISC_mantle_M6.0-10.0', 0),
]
#fnames = [('/data/AlpArray_Data/dmt_database_mantle_0.01-0.2_SKS-phase', 0),
# ('/data/AlpArray_Data/dmt_database_mantle_0.01-0.2_S-phase', 0),]
####
script_location = '/home/marcel/VersionCtrl/git/pylot/pylot/correlation/submit_pick_corr_correction.sh'
####
for fnin, istart in fnames:
input_cmds = f'qsub -q low.q@minos15,low.q@minos14,low.q@minos13,low.q@minos12,low.q@minos11 {script_location} {fnin} {istart}'
print(input_cmds)
print(subprocess.check_output(input_cmds.split()))
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import glob
import json
from obspy import read_events
from pylot.core.util.dataprocessing import Metadata
from pylot.core.util.obspyDMT_interface import qml_from_obspyDMT
def get_event_obspy_dmt(eventdir):
event_pkl_file = os.path.join(eventdir, 'info', 'event.pkl')
if not os.path.exists(event_pkl_file):
raise IOError('Could not find event path for event: {}'.format(eventdir))
event = qml_from_obspyDMT(event_pkl_file)
return event
def get_event_pylot(eventdir, extension=''):
event_id = get_event_id(eventdir)
filename = os.path.join(eventdir, 'PyLoT_{}{}.xml'.format(event_id, extension))
if not os.path.isfile(filename):
return
cat = read_events(filename)
return cat[0]
def get_event_id(eventdir):
event_id = os.path.split(eventdir)[-1]
return event_id
def get_picks(eventdir, extension=''):
event_id = get_event_id(eventdir)
filename = 'PyLoT_{}{}.xml'
filename = filename.format(event_id, extension)
fpath = os.path.join(eventdir, filename)
fpaths = glob.glob(fpath)
if len(fpaths) == 1:
cat = read_events(fpaths[0])
picks = cat[0].picks
return picks
elif len(fpaths) == 0:
print('get_picks: File not found: {}'.format(fpath))
return
print(f'WARNING: Ambiguous pick file specification. Found the following pick files {fpaths}\nFilemask: {fpath}')
return
def write_json(object, fname):
with open(fname, 'w') as outfile:
json.dump(object, outfile, sort_keys=True, indent=4)
def get_metadata(eventdir):
metadata_path = os.path.join(eventdir, 'resp')
metadata = Metadata(inventory=metadata_path, verbosity=0)
return metadata
View File
+23
View File
@@ -0,0 +1,23 @@
from pylot.tomography.fmtomo_utils import Tomo3d
import os
citer = 0
niter = 12
n_proc = 4 # only four processes for minimal example with four sources
# for some reason this did not work as expected and was commented out
#if os.path.isfile('inviter.in'):
# with open('inviter.in', 'r') as infile:
# citer = int(infile.read())
# print ('Continue on iteration step ', citer)
tomo = Tomo3d(os.getcwd(), os.getcwd(), overwrite=True, buildObs=False, saveRays=[6, 12], citer=citer)
try:
tomo.runTOMO3D(n_proc, niter)
except KeyboardInterrupt:
print('runTTOMO3D interrupted by user or machine. Cleaning up.')
except Exception as e:
print(f'Catching unknown Exception in runTOMO3D: {e}. Trying to clean up...')
finally:
tomo.removeDirectories()
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
#
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Small script to compare arrival times of hybrid tau-p/fmm with simple tau-p for standard earth model
import os
import argparse
import numpy as np
import matplotlib.pyplot as plt
from obspy.taup import TauPyModel
from pylot.tomography.fmtomo_tools.fmtomo_teleseismic_utils import organize_receivers, organize_sources, organize_event_names
def read_file(fnin):
infile = open(fnin, 'r')
return infile.readlines()
def compare_arrivals(arrivals_file, receivers_file, sources_file, input_source_file, model='ak135_diehl_v2', exclude_phases=[]):
'''
Reads FMTOMO arrivals.dat file together with corresponding receiver, source and input_source files to match
each arrival to a source and receiver combination, calculate tau-p arrivals for a given earth model and
calculate differences
'''
arrivals_taup = {}
arrivals_tomo = {}
receiver_ids = {}
event_names = {}
events = organize_event_names(input_source_file)
model = TauPyModel(model)
# organize sources and receivers in dictionaries containing their ID used by FMTOMO as dictionary key
receivers_dict = organize_receivers(receivers_file)
sources_dict = organize_sources(sources_file)
# read arrivals file
with open(arrivals_file, 'r') as infile_arrivals:
arrivals = infile_arrivals.readlines()
count = 0
for src_number, source in sources_dict.items():
if source['phase'] in exclude_phases:
continue
src_name = events[src_number - 1]
if not src_name in event_names.keys():
arrivals_taup[src_name] = []
arrivals_tomo[src_name] = []
receiver_ids[src_name] = []
event_names[src_name] = count
count += 1
for line in arrivals:
# read line by line from fmtomo_tools output file arrivals.dat
rec_id, src_id, ray_id, refl, arrival_time, diff, head = line.split()
arrival_time = float(arrival_time)
rec_id = int(rec_id)
src_id = int(src_id)
ray_id = int(ray_id)
# identify receiver and source using dictionary
receiver = receivers_dict[rec_id]
source = sources_dict[src_id]
src_name = events[src_id - 1]
phase = source['phase']
if phase in exclude_phases: continue
taup_arrival = model.get_travel_times_geo(source_depth_in_km=source['depth'],
source_latitude_in_deg=source['lat'],
source_longitude_in_deg=source['lon'],
receiver_latitude_in_deg=receiver['lat'],
receiver_longitude_in_deg=receiver['lon'],
phase_list=[phase])
receiver_depth_in_km = 6371. - receiver['rad']
if len(taup_arrival) == 1:
taup_arrival_time = taup_arrival[0].time
else:
taup_arrival_time = np.nan
arrivals_taup[src_name].append(taup_arrival_time)
arrivals_tomo[src_name].append(arrival_time)
receiver_ids[src_name].append(rec_id)
#plt.plot([min(arrivals_taup),max(arrivals_taup)],[min(arrivals_taup), max(arrivals_taup)], 'k-')
sorted_by_first_arrival = sorted([(src_name, min(arrivals)) for src_name, arrivals in arrivals_taup.items()],
key=lambda x: x[1])
# print some output for analysis
for item in sorted_by_first_arrival:
print(item)
#[print(source) for source in sources_dict.items()]
#[print(item) for item in enumerate(events)]
current_fmtomo_folder_name = os.path.split(os.path.abspath(arrivals_file))[-2]
fname_savefig = '{}'.format(current_fmtomo_folder_name)
if exclude_phases:
fname_savefig += '_e'
for phase in exclude_phases:
fname_savefig += '_{}'.format(phase)
plot_differences(arrivals_taup, arrivals_tomo, sorted_by_first_arrival, fname_savefig)
#for event_name in ['20160124_103037.a_P.ttf', '20160729_211833.a_Pdiff.ttf', '20160729_211833.a_P.ttf']:
# plot_event(arrivals_tomo, arrivals_taup, receiver_ids, receivers_dict, src_name=event_name)
def plot_differences(arrivals_taup, arrivals_tomo, sorted_by_first_arrival, fname_savefig):
fig = plt.figure(figsize=(16,9))
ax = fig.add_subplot(111)
# init plot for tt differences
cmap = plt.get_cmap('jet')
colors = cmap(np.linspace(0, 1, len(sorted_by_first_arrival)))
for index, item in enumerate(sorted_by_first_arrival):
src_name = item[0]
ax.scatter(arrivals_taup[src_name], np.array(arrivals_tomo[src_name]) - np.array(arrivals_taup[src_name]),
c=colors[index], s=25, marker='.', label=src_name, edgecolors='none')
# shrink box for legend
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
ax.legend(bbox_to_anchor=[1, 1], loc='upper left')
plt.title(fname_savefig)
ax.set_xlabel('Absolute time $t_{tau-p}$')
ax.set_ylabel('Time difference $t_{hybrid} - t_{tau-p}$')
print('Saving plot to {}.png'.format(fname_savefig))
fig.savefig(fname_savefig + '.png', dpi=300)
#plt.show()
def plot_event(arrivals_tomo, arrivals_taup, receiver_ids_dict, receivers_dict, src_name):
arrivals_diff = np.array(arrivals_tomo[src_name]) - np.array(arrivals_taup[src_name])
receiver_ids = receiver_ids_dict[src_name]
x = np.array([receivers_dict[rec_id]['lon'] for rec_id in receiver_ids])
y = np.array([receivers_dict[rec_id]['lat'] for rec_id in receiver_ids])
sc = plt.scatter(x, y, c=arrivals_diff, edgecolor='none')
plt.xlabel('Longitude [deg]')
plt.ylabel('Latitude [deg]')
cbar = plt.colorbar(sc)
cbar.ax.set_ylabel('traveltime difference ($t_{hybrid} - t_{tau-p}$)')
plt.title('{}'.format(src_name))
plt.show()
# folders=[
# 'alparray_0_receiver_elev_zero',
# 'alparray_0_receiver_elev_zero_finer_pgrid_vgrid_r',
# 'alparray_0_receiver_elev_zero_finer_vgrid_llr',
# 'alparray_0_receiver_elev_zero_smaller_box',
# 'alparray_0_receiver_elev_zero_shallow_box',
# 'alparray_0_receiver_elev_zero_finer_pgrid_llr',
# 'alparray_0_receiver_elev_zero_finer_interface',
# #'alparray_0_receiver_elev_zero_bigger_box',
# ]
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Compare arrivals with TauP-times')
parser.add_argument('fmtomodir', help='path containing fm3d output')
parser.add_argument('-e', dest='exclude', default=[], help='exclude phases, comma separated, no spaces')
args = parser.parse_args()
fdir = args.fmtomodir
arrivals_file = os.path.join(fdir, 'arrivals.dat')
receivers_file = os.path.join(fdir, 'receivers.in')
sources_file = os.path.join(fdir, 'sources.in')
input_source_file = os.path.join(fdir, 'input_source_file_P.in')
exclude_phases = args.exclude
if exclude_phases:
exclude_phases = exclude_phases.split(',')
compare_arrivals(arrivals_file, receivers_file, sources_file, input_source_file=input_source_file, exclude_phases=exclude_phases)
@@ -0,0 +1,39 @@
import os
cwdir = '/data/AlpArray_Data/fmtomo/v5/tradeoff_curves'
parent_dir_name = 'crust_included_grad_smooth_FIXED_dts'#_grad_1.5'
dampings = [3., 10., 30.]#, 30.]
smoothings = [5.6]
def main(submit_run=True):
fdir_parent = os.path.join(cwdir, parent_dir_name)
for damp in dampings:
for smooth in smoothings:
fdir_out = fdir_parent + '_sm{}_damp{}'.format(smooth, damp)
if not os.path.isdir(fdir_out):
os.mkdir(fdir_out)
os.system('cp -P {}/* {}'.format(fdir_parent, fdir_out))
invertfile = os.path.join(fdir_out, 'invert3d.in')
modify_invert_in(invertfile, damp, smooth)
if submit_run:
os.chdir(fdir_out)
os.system('qsub submit_fmtomo.sh')
def modify_invert_in(fnin, damp, smooth):
with open(fnin, 'r') as infile:
lines = infile.readlines()
with open(fnin, 'w') as outfile:
for line in lines:
if not line.startswith('c'):
value, comment = line.split('c:')
if 'Global damping' in comment:
line = line.replace(value.strip(), str(damp) + ' ')
elif 'Global smoothing' in comment:
line = line.replace(value.strip(), str(smooth) + ' ')
outfile.write(line)
if __name__ == '__main__':
main()
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
from obspy.geodetics.base import gps2dist_azimuth
def main(infile):
per = 4e4 # earth perimeter
fid = open(infile, 'r')
nrec = fid.readline()
latsrc, lonsrc, depsrc = [float(value) for value in fid.readline().split()]
phase = fid.readline()
latrec, lonrec, deprec = [float(value) for value in fid.readline().split()[:3]]
print ('Lat/Lon Source: {} / {}'.format(latsrc, lonsrc))
print ('Lat/Lon Receiver: {} / {}'.format(latrec, lonrec))
dist_deg = gps2dist_azimuth(latsrc, lonsrc, latrec, lonrec)[0] / 1e3 / per * 360
print ('Distance: {} [deg]'.format(dist_deg))
if __name__ == "__main__":
parser = argparse.ArgumentParser('Estimate distance from source to first'
' receiver in WGS84 ellipsoid in FMTOMO pick file.')
parser.add_argument('infile', help='FMTOMO pickfile (*.ttf)')
args = parser.parse_args()
main(args.infile)
@@ -0,0 +1,185 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import glob, os, shutil
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
from obspy.geodetics import gps2dist_azimuth
#pwd = '/rscratch/minos13/marcel/fmtomo_alparray/v3.5/alparray_events_thinned/picks'
pwd = '/data/AlpArray_Data/fmtomo/v6/crust_incl_hf_sm_FIX_DTS_grad_sm30_dm10_EASI_test_Plomerova_NS_events/picks'
os.chdir(pwd)
infiles = glob.glob('*.ttf')
clat=46.
clon=11.
ddist = 5
dazim = 5
def make_grid(ddist, dazim):
distgrid = np.arange(35, 135, ddist)
bazimgrid = np.arange(0, 360, dazim)
grid = []
for bazim in np.deg2rad(bazimgrid):
for dist in distgrid:
grid.append(np.array([bazim, dist]))
return np.array(grid)
def make_axes():
fig = plt.figure(figsize=(16,9))
ax1 = fig.add_subplot(121, projection='polar')
ax2 = fig.add_subplot(122)
ax1.set_theta_direction(-1)
ax1.set_theta_zero_location('N')
return ax1, ax2
def load_events():
events = {}
for infile in infiles:
with open(infile, 'r') as fid:
eventid = infile.split('.ttf')[0]
npicks = int(fid.readline())
lat, lon, depth = [float(item) for item in fid.readline().split()]
dist, bazim, azim = gps2dist_azimuth(clat, clon, lat, lon, a=6.371e6, f=0)
bazim = np.deg2rad(bazim)
dist = dist / (np.pi * 6371) * 180 / 1e3
events[eventid] = dict(dist=dist, bazim=bazim, npicks=npicks)
return events
def get_events_in_grid():
events_in_grid = []
for index, gcorner in enumerate(grid):
bazim_l, dist_l = gcorner
bazim_u = bazim_l + np.deg2rad(dazim)
dist_u = dist_l + ddist
events_in_grid.append(dict(bazims=(bazim_l, bazim_u), dists=(dist_l, dist_u), events=[]))
for eventid, event in events.items():
if (dist_l <= event['dist'] < dist_u) and (bazim_l <= event['bazim'] <= bazim_u):
events_in_grid[index]['events'].append(eventid)
return events_in_grid
def filter_events():
filtered_events = {}
for eventdict in events_in_grid:
cur_events = eventdict['events']
if not cur_events: continue
eventid = get_best_event(cur_events)
filtered_events[eventid] = events[eventid]
return filtered_events
def get_best_event(cur_events):
''' return eventid with highest number of picks'''
select_events = {key: events[key] for key in cur_events}
npicks = {key: value['npicks'] for key, value in select_events.items()}
eventid = max(npicks, key=npicks.get)
return eventid
def plot_distribution(events_dict):
cmap_bnd = plt.get_cmap('Greys_r')
cmap_center = plt.get_cmap('viridis')
nevents = [len(grid_dict['events']) for grid_dict in events_dict]
npicks = []
for ev_dict in events_dict:
npick = 0
for eventid in ev_dict['events']:
npick += events[eventid]['npicks']
npicks.append(npick)
npicks = np.array(npicks)
ev_max = max(nevents)
np_max = max(npicks)
print('N picks total:', np.sum(npicks))
print('N picks max: ', np_max)
print('N events max: ', ev_max)
ax_polar, ax_hist = make_axes()
patches = []
for npick, ev_dict in zip(npicks, events_dict):
bazim_l, bazim_u = ev_dict.get('bazims')
dist_l, dist_u = ev_dict.get('dists')
n_ev = len(ev_dict.get('events'))
color_edge = cmap_bnd(n_ev / ev_max)
color_center = cmap_center(npick / np_max)
# color = cmap(np.random.rand())
rect = Rectangle((bazim_l, dist_l), np.deg2rad(dazim), ddist, edgecolor=color_edge)#, facecolor=color_center)
patches.append(rect)
collection = PatchCollection(patches, cmap=cmap_center)
collection.set_array(npicks)
ax_polar.add_collection(collection)
ax_polar.set_ylim((10, 135))
cbar = plt.colorbar(collection)
cbar.set_label('N picks')
# ax.scatter(grid[:, 0] + 0.5 * dazim, grid[:, 1] + 0.5 * ddist, c=nevents, s=50)
bazims = []
dists = []
for event in events.values():
bazims.append(event.get('bazim'))
dists.append(event.get('dist'))
ax_polar.scatter(bazims, dists, c='k', zorder=3, s=5, alpha=0.5)
ax_hist.hist(np.rad2deg(bazims), bins=np.arange(0, 360, dazim))
ax_hist.set_xlabel('Backazimuth (deg)')
ax_hist.set_ylabel('Number of events')
plt.title('Polar event distribution and histogram of backazimuths')
def export_filtered_events(fdir_save='picks_save'):
if not os.path.isdir(fdir_save):
os.mkdir(fdir_save)
for infile in infiles:
eventid = infile.split('.ttf')[0]
if not eventid in events:
for fname in glob.glob('{}.*'.format(eventid)):
shutil.move(fname, fdir_save)
print('Moved file {} to path {}'.format(fname, fdir_save))
def write_input_source_file(fname='input_source_file_P_new.in'):
with open(fname, 'w') as outfile:
outfile.write('{}\n'.format(len(events)))
for eventid in sorted(list(events.keys())):
outfile.write('1 1 {}.ttf\n'.format(eventid))
def filter_bazim(events, bazims_list):
events_filtered = {}
for eventid, event_dict in events.items():
for baz_min, baz_max in bazims_list:
if baz_min <= event_dict['bazim'] * 180. <= baz_max:
events_filtered[eventid] = event_dict
return events_filtered
filter_bazims = [(330, 360), (0, 30), (150, 210)]
events = load_events()
#plot_distribution(events)
events = filter_bazim(events, bazims_list=filter_bazims)
print()
#grid = make_grid(ddist, dazim)
#events_in_grid = get_events_in_grid()
#plot_distribution()
#events = filter_events()
#events_in_grid = get_events_in_grid()
#plot_distribution(events)
#plt.show()
export_filtered_events()
write_input_source_file()
@@ -0,0 +1,28 @@
import os
import numpy as np
from obspy.geodetics import gps2dist_azimuth
from fmtomo_tools.fmtomo_teleseismic_utils import organize_sources
fmtomodir = '/data/AdriaArray_Data/fmtomo_adriaarray/alpadege/no_crust_correction'
clon = 17.5
clat = 42.25
os.chdir(fmtomodir)
sources = organize_sources('sources.in')
dists = []
lats = []
lons = []
for source_id, source_dict in sources.items():
slat = source_dict['lat']
slon = source_dict['lon']
lats.append(slat)
lons.append(slon)
dist_m = gps2dist_azimuth(slat, slon, clat, clon, a=6.371e6, f=0)[0]
dist = dist_m / (np.pi * 6371) * 180 / 1e3
dists.append(dist)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import argparse
from fmtomo_teleseismic_utils import setup_fmtomo_sim
#setup_fmtomo_sim('/data/AlpArray_Data/dmt_database/', '//data/AlpArray_Data/fmtomo/v6_S/crust_incl_hf_sm_FIX_DTS_grad_sm30_dm10')
#setup_fmtomo_sim('/rscratch/minos13/marcel/dmt_database_test_event', '/home/marcel/marcel_scratch/alparray/fmtomo_traveltime_tomo/alparray_0/')
#setup_fmtomo_sim('/rscratch/minos13/marcel/dmt_database_m7', '/home/marcel/marcel_scratch/alparray/fmtomo_traveltime_tomo/alparray_0/')
#setup_fmtomo_sim('/rscratch/minos22/marcel/dmt_database_m7', '/rscratch/minos22/marcel/alparray/fmtomo_traveltime_tomo/alparray_0_receiver_elev_zero_smaller_box')
#setup_fmtomo_sim('/rscratch/minos22/marcel/dmt_database_m7', '/rscratch/minos22/marcel/alparray/fmtomo_traveltime_tomo/alparray_0_receiver_elev_zero_bigger_box')
#setup_fmtomo_sim('/rscratch/minos22/marcel/dmt_database_m7', '/rscratch/minos22/marcel/alparray/fmtomo_traveltime_tomo/alparray_0_receiver_elev_zero_shallow_box')
#setup_fmtomo_sim('/rscratch/minos22/marcel/dmt_database_m7', '/rscratch/minos22/marcel/alparray/fmtomo_traveltime_tomo/alparray_0_receiver_elev_zero_finer_interface')
#setup_fmtomo_sim('/rscratch/minos22/marcel/dmt_database_m7', '/rscratch/minos22/marcel/alparray/fmtomo_traveltime_tomo/alparray_0_receiver_elev_zero_bigger_box_equal_dist_2')
#pgrid = Propgrid('/rscratch/minos22/marcel/alparray/fmtomo_traveltime_tomo/alparray_0/propgrid.in')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Prepare grid for fm3d teleseismic hybrid calculation.')
parser.add_argument('dmt_path', help='path containing dmt_database with PyLoT picks')
parser.add_argument('fmtomodir', help='path containing fm3d output')
parser.add_argument('fname_extension', help='filename extension of pick files')
parser.add_argument('--blacklist', default=None,
help='station blacklist file (csv). After first line: NW_id,ST_id in each line')
parser.add_argument('--no_write_init_nodes', default=False, action='store_true',
help='do not calculate and write init nodes')
parser.add_argument('--no_recalculate_init_nodes', default=False, action='store_true',
help='do not recalculate init nodes if nodes file exists')
parser.add_argument('-n', dest='ncores', default=None, help='number of cores for multiprocessing')
parser.add_argument('--model', default='ak135')
parser.add_argument('--s_phase', default=False, action='store_true')
args = parser.parse_args()
database_path = os.path.abspath(args.dmt_path)
fmtomodir = os.path.abspath(args.fmtomodir)
if args.ncores is not None:
ncores = int(args.ncores)
else:
ncores = None
fname_extension = args.fname_extension
sub_phases = {'P': ['P', 'PKP'], 'S': ['S', 'SKS']}
phase_types = ['P']
if args.s_phase:
phase_types.append('S')
setup_fmtomo_sim(database_path, fmtomodir, fname_extension, sub_phases, ncores=ncores, check_notesfile=False,
model=args.model, fname_station_blacklist=args.blacklist,
no_write_init_nodes=args.no_write_init_nodes,
no_recalculate_init_nodes=args.no_recalculate_init_nodes, phase_types=phase_types)
@@ -0,0 +1,930 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import subprocess
import warnings
import os
import glob
from datetime import datetime
import multiprocessing
import numpy as np
import json
from obspy import read_events, UTCDateTime
from obspy.taup import TauPyModel
from obspy.geodetics import locations2degrees, gps2dist_azimuth
from pylot.core.util.utils import identifyPhaseID
from pylot.core.util.obspyDMT_interface import qml_from_obspyDMT
from pylot.tomography.utils import pol2cart, pol2cart_vector
from pylot.tomography.utils import get_metadata
class Propgrid(object):
'''
small class that is built from an fm3d propgrid.in file; generates a regular grid in the same way as fm3d
'''
def __init__(self, filename):
self.r_earth = 6371. # earth radius in km
self.init_propgrid(filename)
def init_propgrid(self, filename_propgrid):
self.read_propgrid(filename_propgrid)
self.init_rGrid()
self.init_latGrid()
self.init_longGrid()
def init_rGrid(self):
self.rs = np.zeros(self.nR)
self.rbot = self.r_earth + self.zTop - (self.nR - 1) * self.deltaR
for index in range(self.nR):
self.rs[index] = self.rbot + index * self.deltaR
def init_latGrid(self):
self.lats = np.zeros(self.nLat)
self.latS = np.deg2rad(self.latSdeg)
self.deltaLat = np.deg2rad(self.deltaLatDeg)
for index in range(self.nLat):
self.lats[index] = self.latS + index * self.deltaLat
def init_longGrid(self):
self.longs = np.zeros(self.nLong)
self.longW = np.deg2rad(self.longWdeg)
self.deltaLong = np.deg2rad(self.deltaLongDeg)
for index in range(self.nLong):
self.longs[index] = self.longW + index * self.deltaLong
def read_propgrid(self, filename_propgrid):
infile = open(filename_propgrid, 'r')
self.nR, self.nLat, self.nLong = [int(value) for value in infile.readline().split()]
self.deltaR, self.deltaLatDeg, self.deltaLongDeg = [np.float64(value) for value in infile.readline().split()]
self.zTop, self.latSdeg, self.longWdeg = [np.float64(value) for value in infile.readline().split()]
infile.close()
def check_event_notes(eventdir):
eventfile = os.path.join(eventdir, 'notes.txt')
if os.path.isfile(eventfile):
with open(eventfile, 'r') as infile:
notes = infile.readline()
print(notes)
if 'exclude' in notes:
print('Will exclude this event!')
return False
else:
print('No notes file found.')
return True
def prepare_fmtomo_dir_first_run(fmtomo_dir):
"""
This helper function calls the file 'fm3d_prepare_tele' in the fmtomo binary after creating a "fake" receivers.in
and sources.in file so that fm3d can prepare the teleseismic run and writes the file "init_nodes.out" that contains
information on the boundary nodes of the current propagation grid. Make sure that "grid3dg" has already been called
for this function to work.
:param fmtomo_dir:
:return:
"""
def write1(fid):
fid.write('1\n')
def write_rec_file():
""" Writes a fake receiver (has to be inside the grid) """
with open(recfile, 'w') as fid:
write1(fid)
fid.write(f'0 {clat} {clon}\n')
for _ in range(3):
write1(fid)
def write_src_file():
""" Writes a fake source """
fakesrc_str = '''
1
1
P
0.0000 0.0000 0.0000 0.00000 0.00000 0.00000
1
1
2 1
1
1
'''
with open(srcfile, 'w') as fid:
fid.write(fakesrc_str)
def get_clat_clon():
if not os.path.isfile(propgrid_file):
raise IOError(f'Missing file {propgrid_file} for fmtomo first run preparation.')
with open(propgrid_file, 'r') as fid:
_, nlat, nlon = (int(item) for item in fid.readline().split())
_, dlat, dlon = (float(item) for item in fid.readline().split())
_, lat0, lon0 = (float(item) for item in fid.readline().split())
clat = lat0 + nlat / 2 * dlat
clon = lon0 + nlon / 2 * dlon
return clat, clon
# check if binary file exists
fmtomo_prep_tele = os.path.join(fmtomo_dir, 'fm3d_prepare_tele')
assert os.path.isfile(fmtomo_prep_tele), f'Missing binary file {fmtomo_prep_tele}'
# set filenames for propgrid, sources and receivers
propgrid_file = os.path.join(fmtomo_dir, 'propgrid.in')
recfile = os.path.join(fmtomo_dir, 'receivers.in')
srcfile = os.path.join(fmtomo_dir, 'sources.in')
# get coords from propgrid
clat, clon = get_clat_clon()
# write fake source and receiver files
write_src_file()
write_rec_file()
# execute fm3d_prepare tele from local directory
curdir = os.getcwd()
os.chdir(fmtomo_dir)
rval = subprocess.check_output([fmtomo_prep_tele]).decode('utf-8')
if not rval.split('\n')[-2] == ' Finished teleseismic preparation. Stop.':
raise ValueError('Unexpected output initialisation run.')
if not os.path.isfile(os.path.join(fmtomo_dir, 'init_nodes.out')):
raise Exception('Could not create output file init_nodes.')
os.chdir(curdir)
# clean up
os.remove(srcfile)
os.remove(recfile)
print('Prepare fmtomo dir first run: Success')
def setup_fmtomo_sim(database_path_dmt, fmtomo_dir, fname_extension, sub_phases, ncores=None, model='ak135',
min_picks_per_phase=10, write_vtk=True, check_notesfile=True, fname_station_blacklist=None,
no_write_init_nodes=False, no_recalculate_init_nodes=False, phase_types=('P, S')):
'''
main function of this program, writes picks and input source file for FMTOMO obsdata program
'''
assert os.path.isdir(database_path_dmt), 'Unrecognized directory {}'.format(database_path_dmt)
assert os.path.isdir(fmtomo_dir), 'Unrecognized directory {}'.format(fmtomo_dir)
tstart = datetime.now()
print('Starting script at {}'.format(tstart))
print('Check notesfile set to {}'.format(check_notesfile))
# save means of event traveltimes to dictionary for statistical analysis
#tt_means = {}
fname_fmtomo_nodes = os.path.join(fmtomo_dir, 'init_nodes.out')
# do first initialisation of FMTOMO to generate init_nodes file if required
if not no_recalculate_init_nodes:
prepare_fmtomo_dir_first_run(fmtomo_dir)
eventdirs = glob.glob(os.path.join(database_path_dmt, '*.?'))
nEvents = len(eventdirs)
# create directory that will contain the picks
picksdir = os.path.join(fmtomo_dir, 'picks')
if not os.path.isdir(picksdir):
os.mkdir(picksdir)
# track and count ALL source locations & phases (P and S) for fmtomo_tools
associations_str = ''
nsrc_total = 0
# iterate over P and S to create one model each
for phase_type in phase_types:
sourcefile_str = ''
nsrc = 0
# iterate over all events in "database_path_dmt"
for eventindex, eventdir in enumerate(eventdirs):
print('Working on {}-picks for event {} ({}/{})'.format(phase_type, eventdir,
eventindex + 1, len(eventdirs)))
if check_notesfile and not check_event_notes(eventdir):
continue
# create ObsPy event from .pkl file in dmt eventdir
event = get_event_obspy_dmt(eventdir)
if len(event.origins) > 1:
raise Exception('Ambiguous origin information for event {}'.format(event))
origin = event.origins[0]
# get all picks from PyLoT *.xml file
picks = get_picks(eventdir, extension=fname_extension)
if not picks:
print('No picks for event {} found.'.format(eventdir))
continue
# remove picks for blacklisted stations
if fname_station_blacklist:
picks, n_deleted_blacklist = remove_blacklisted_station_picks(picks, fname_station_blacklist)
# get metadata for current event from dmt_database
metadata = get_metadata(eventdir)
# get a dictionary containing coordinates for all stations
stations_dict = metadata.get_all_coordinates()
# catch event id
event_id = get_event_id(eventdir)
# map specific phases to another phase (Pdiff -> P)
merge_phases = {'Pdiff': 'P'}
for key, value in merge_phases.items():
print('Will map phase {} to phase {} if present.'.format(key, value))
# assign all picks of this event to a phase and add to sorted_picks dictionary
sorted_picks = sort_picks_by_phase(picks, stations_dict, origin,
sub_phases[phase_type], model, merge_phases=merge_phases)
sorted_picks = remove_uncommon_picks(sorted_picks, min_picks_per_phase)
# the following should not be necessary when calculating traveltimes on borders using TauPy
#sorted_picks = translate_phase_names_TAUP(sorted_picks)
#
# iterate over sorted picks and write a traveltime file for each phase type
for phase, picks_list in sorted_picks.items():
pickfile_base = '{}_{}'.format(event_id, phase)
pickfile_name = pickfile_base + '.ttf'
init_nodes_name = pickfile_base + '.nodes'
vtk_file_name = pickfile_base + '.vtk'
pickfile = os.path.join(picksdir, pickfile_name)
init_nodes_file = os.path.join(picksdir, init_nodes_name)
vtk_file = os.path.join(picksdir, vtk_file_name)
# remove source time
picks_list = subtract_source_time(picks_list, origin.time)
# save mean for statistical analysis
#tt_means[pickfile_base] = mean
# create a list with all "true" phases used for taupy to calculate boundary ttimes/ray parameters
phases = list(set([item['true_phase'] for item in picks_list]))
if no_write_init_nodes == False:
if no_recalculate_init_nodes and os.path.isfile(init_nodes_file):
print('Found previously calculated init nodes. Will not recalculate.')
else:
# calculate travel times and ray parameters for boundary nodes and write to file
points_list = initNodesFm3d(fname_fmtomo_nodes, origin, model, phases, ncores=ncores)
# in case initNodes fails and returns None continue with next phase
if not points_list:
print('No points list, initNodesFm3d failed for event.')
continue
if not write_init_points(points_list, init_nodes_file):
print('Write init points failed for event.')
continue
if write_vtk==True:
write_vtk_file(points_list, vtk_file)
# write picks to pickfile for fm3d
write_picks_to_pickfile(pickfile, phase, picks_list, stations_dict, origin)
# add pickfile to sourcefile string and count number of pickfiles
sourcefile_str += '1 1 {}\n'.format(pickfile_name)
# add source location, phaseID and .nodes filename to association string
source_string = '{phase} {lat} {lon} {depth} {fname}\n'
associations_str += source_string.format(phase=phase, lat=origin.latitude,
lon=origin.longitude, depth=origin.depth,
fname=init_nodes_name)
nsrc += 1
nsrc_total += 1
average_time = (datetime.now() - tstart) / (eventindex + 1)
print('Average time for event (phase {}): {}'.format(phase_type, average_time))
print('ETA for {}-events: {}'.format(phase_type, tstart + nEvents * average_time))
write_src_file(fmtomo_dir, phase_type, nsrc, sourcefile_str)
write_assc_file(fmtomo_dir, nsrc_total, associations_str)
#write_json(tt_means, os.path.join(fmtomo_dir, 'ttmeans.json'))
print('Script finished! Good Bye!')
def write_src_file(fmtomo_dir, phase_type, nsrc, sourcefile_str):
# write input_source_file for obsdata
input_source_file = open(os.path.join(fmtomo_dir, 'input_source_file_{}.in'.format(phase_type)), 'w')
input_source_file.write('{}\n'.format(nsrc))
input_source_file.write(sourcefile_str)
input_source_file.close()
def write_assc_file(fmtomo_dir, nsrc_total, associations_str):
# write input_associations file for fm3d
input_assc_file = open(os.path.join(fmtomo_dir, 'input_associations_file.in'), 'w')
input_assc_file.write('{}\n'.format(nsrc_total))
input_assc_file.write(associations_str)
input_assc_file.close()
def write_json(object, fname):
with open(fname, 'w') as outfile:
json.dump(object, outfile, sort_keys=True, indent=4)
def write_vtk_file(points_list, fname):
outfile = open(fname, 'w')
nPoints = len(points_list)
outfile.write('# vtk DataFile Version 2.0\n')
outfile.write('FMM Init points\n')
outfile.write('ASCII\n')
outfile.write('DATASET POLYDATA\n')
outfile.write('POINTS {} float\n'.format(nPoints))
for point in points_list:
lat = point['pt_lat']
lon = point['pt_lon']
rad = point['pt_R']
x, y, z = pol2cart(lat, lon, rad)
outfile.write('{x} {y} {z}\n'.format(x=x, y=y, z=z))
# write number of vertices and their indices
outfile.write('\nVERTICES {} {}\n'.format(nPoints, 2*nPoints))
for index, point in enumerate(points_list):
outfile.write('{} {}\n'.format(1, index))
# write header with number of data points
outfile.write('\nPOINT_DATA {}\n'.format(nPoints))
# write header for traveltimes
outfile.write('SCALARS traveltime float 1\n')
outfile.write('LOOKUP_TABLE default\n')
for point in points_list:
time = point.get('time')
outfile.write('{}\n'.format(time if time else 0))
# write header for point indices
outfile.write('SCALARS point_index integer 1\n')
outfile.write('LOOKUP_TABLE default\n')
for point in points_list:
pt_index = point.get('pt_index')
outfile.write('{}\n'.format(pt_index if pt_index else 0))
outfile.write('VECTORS tt_grad float\n')
for point in points_list:
lat = point['pt_lat']
lon = point['pt_lon']
r_comp = point.get('ray_param_km_z_comp')
n_comp = point.get('ray_param_km_n_comp')
e_comp = point.get('ray_param_km_e_comp')
sx, sy, sz = pol2cart_vector(lat, lon, n_comp, e_comp, r_comp)
outfile.write('{sx} {sy} {sz}\n'.format(sx=sx if sx else 0.,
sy=sy if sy else 0.,
sz=sz if sz else 0.,))
outfile.close()
def write_init_points(points_list, outfile):
fid = open(outfile, 'w')
print('Writing {} init points to file {}.'.format(len(points_list), outfile))
output_str = ''
# count number of points for file header
npoints = 0
for point_dict in points_list:
nArrivals = point_dict.get('nArrivals')
if not nArrivals:
#print('No arrivals for point:')
#print(point_dict)
continue
if nArrivals > 1:
fid.close()
os.remove(outfile)
warning_template = 'Ambiguous information for point: {}, having {} different arrivals. Skipping event.'
print(warning_template.format(point_dict, nArrivals))
return False
output_template = '{index} {time} {ray_param_z} {ray_param_n} {ray_param_e}\n'
output_str += (output_template.format(index=point_dict['pt_index'],
time=point_dict.get('time'),
ray_param_z=point_dict.get('ray_param_km_z_comp'),
ray_param_n=point_dict.get('ray_param_km_n_comp'),
ray_param_e=point_dict.get('ray_param_km_e_comp')))
npoints += 1
fid.write('{}\n'.format(npoints))
fid.write(output_str)
fid.close()
return True
def initNodesFm3d(filename_init_nodes, source_origin, model, phases, min_dist=25., ncores=None, R=6371000.):
# This function uses obspy TauPy to calculate travel times and ray parameters on boundary points of the fm3d grid
def exposed_sides(grid_boundaries, src_lat, src_lon):
# check which sides of grid are exposed to source (taken from fm3d teleseismic.f90 code)
# here: baz is calculated, not az
north = grid_boundaries['north']
east = grid_boundaries['east']
south = grid_boundaries['south']
west = grid_boundaries['west']
# north
baz1 = gps2dist_azimuth(north, west, src_lat, src_lon, a=R, f=0)[1]
baz2 = gps2dist_azimuth(north, east, src_lat, src_lon, a=R, f=0)[1]
exposed_north = all(np.cos(np.deg2rad(baz)) > 0.01 for baz in [baz1, baz2])
# east
baz1 = gps2dist_azimuth(north, east, src_lat, src_lon, a=R, f=0)[1]
baz2 = gps2dist_azimuth(south, east, src_lat, src_lon, a=R, f=0)[1]
exposed_east = all(np.sin(np.deg2rad(baz)) > 0.01 for baz in [baz1, baz2])
# south
baz1 = gps2dist_azimuth(south, west, src_lat, src_lon, a=R, f=0)[1]
baz2 = gps2dist_azimuth(south, east, src_lat, src_lon, a=R, f=0)[1]
exposed_south = all(np.cos(np.deg2rad(baz)) < -0.01 for baz in [baz1, baz2])
# west
baz1 = gps2dist_azimuth(north, west, src_lat, src_lon, a=R, f=0)[1]
baz2 = gps2dist_azimuth(south, west, src_lat, src_lon, a=R, f=0)[1]
exposed_west = all(np.sin(np.deg2rad(baz)) < -0.01 for baz in [baz1, baz2])
exposed_dict = dict(north=exposed_north, east=exposed_east, south=exposed_south, west=exposed_west,
rbot=True)
return exposed_dict
def point_valid(pt_lat, pt_lon, pt_R, exposed_dict, grid_boundaries, R_earth):
# check whether point has to be active or not depending on its position to the source
def check_point(actual, desired, threshold=0.001):
# checks receiver (boundary) point orientation by comparison to corner boundary points
return abs(actual - desired) < threshold
# check for negative depth (above surface)
if pt_R > R_earth:
return False
# check if point belongs to bottom interface
#if check_point(pt_R, grid_boundaries['rbot']):
# return True
pt_faces_dir = {}
pt_exposed_dir = {}
directions = {'north': pt_lat,
'east': pt_lon,
'south': pt_lat,
'west': pt_lon,
'rbot': pt_R,}
for direction, pt_lat_or_lon in directions.items():
# check if point belongs to boundary of this direction and save to pt_faces_dir
pt_direction_check = check_point(pt_lat_or_lon, grid_boundaries[direction])
pt_faces_dir[direction] = pt_direction_check
# check if point is exposed to source in this direction and save to pt_exposed_dir
pt_exposed_dir[direction] = pt_direction_check and exposed_dict[direction]
# compare number of points facing source direction to the actual direction they are facing
return sum(pt_faces_dir.values()) == sum(pt_exposed_dir.values())
import datetime
now = datetime.datetime.now()
infile = open(filename_init_nodes, 'r')
R_earth = 6371.
# read input files containing fm3d boundary points in each line in the order: lat lon radius
init_nodes = infile.readlines()
nPoints = len(init_nodes)
# split lines and convert to floats except for first value which is converted to int
init_nodes = [[float(val) if index != 0 else int(val) for index, val in enumerate(line.split())]
for line in init_nodes]
grid_boundaries = dict(north=max(init_nodes, key=lambda x: x[1])[1],
east=max(init_nodes, key=lambda x: x[2])[2],
south=min(init_nodes, key=lambda x: x[1])[1],
west=min(init_nodes, key=lambda x: x[2])[2],
rbot=min(init_nodes, key=lambda x: x[3])[3],
rtop=max(init_nodes, key=lambda x: x[3])[3])
# get source coordinates
src_lat, src_lon, src_depth = source_origin.latitude, source_origin.longitude, source_origin.depth
#src_lat = 0; src_lon = 180; src_depth=50
# calculate which sides are exposed to source
exposed_dict = exposed_sides(grid_boundaries, src_lat, src_lon)
# iterate over all points and calculate distance. Append a dictionary to an input list for
# multiprocessing worker for each point
points_list = []
for point in init_nodes:
pt_index, pt_lat, pt_lon, pt_R = point
pt_depth = R_earth - pt_R
dist = locations2degrees(pt_lat, pt_lon, src_lat, src_lon)
# check minimum distance for this point
if dist < min_dist:
warnings.warn('Distance for point {} less than minimum'
' distance ({} km). Skipping event!'.format(point, min_dist))
return
# check if point is exposed to source and has to be initiated
if not point_valid(pt_lat, pt_lon, pt_R, exposed_dict, grid_boundaries, R_earth):
continue
input_dict = {'pt_index': pt_index,
'pt_depth': pt_depth,
'pt_R': pt_R,
'pt_lat': pt_lat,
'pt_lon': pt_lon,
'src_lat': src_lat,
'src_lon': src_lon,
'src_depth': src_depth,
'dist': dist,
'model': model,
'phases': phases,
}
points_list.append(input_dict)
print('n Points init:', len(points_list))
#plot_points(points_list)
rvals = []
pool = multiprocessing.Pool(ncores, maxtasksperchild=1000)
for rval in pool.imap(taup_worker, points_list, chunksize=10):
rvals.append(rval)
pool.close()
pool.join()
print('Done after {}'.format(datetime.datetime.now() - now))
#plot_points(points_list)
return rvals
def taup_worker(input_dict):
# initiate model for TauP method
model = TauPyModel(model=input_dict['model'])
huge_time = 1e7
try:
arrivals = model.get_ray_paths(input_dict['src_depth'], input_dict['dist'],
phase_list=input_dict['phases'],
receiver_depth_in_km=input_dict['pt_depth'], )
if len(arrivals) < 1:
#print('No arrivals for phase {}'.format(input_dict['phase']))
#print(input_dict)
return input_dict
arr = arrivals[0]
input_dict['nArrivals'] = len(arrivals)
baz = gps2dist_azimuth(input_dict['pt_lat'], input_dict['pt_lon'],
input_dict['src_lat'], input_dict['src_lon'],
a=6371.*1e3, f=0)[1]
# calculate traveltime gradient, division by R to transform from rad to km
# multiply with -1 to get values for azimuth instead of back-azimuth
ray_param_km_z_comp = arr.ray_param / np.tan(np.deg2rad(arr.incident_angle)) / input_dict['pt_R']
ray_param_km_n_comp = arr.ray_param * (-1) * np.cos(np.deg2rad(baz)) / input_dict['pt_R']
ray_param_km_e_comp = arr.ray_param * (-1) * np.sin(np.deg2rad(baz)) / input_dict['pt_R']
input_dict['ray_param_km_z_comp'] = ray_param_km_z_comp
input_dict['ray_param_km_n_comp'] = ray_param_km_n_comp
input_dict['ray_param_km_e_comp'] = ray_param_km_e_comp
input_dict['time'] = arr.time
except Exception as e:
print(e, input_dict)
return input_dict
def plot_points(points_list):
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = []
y = []
z = []
color = np.full(len(points_list), np.nan)
for index, points_dict in enumerate(points_list):
x.append(points_dict['pt_lat'])
y.append(points_dict['pt_lon'])
z.append(points_dict['pt_depth'])
arrivals = points_dict.get('arrivals')
if arrivals:
color[index] = arrivals[0].time
ax.scatter(x, y, z, c=color)
plt.show()
def subtract_source_time(picks_list, origin_time):
# WAS FUNCTION DEMEAN, but demean after calculating residuals using rtimes_tele in FMTOMO!!
for phase_dict in picks_list:
taup_time = phase_dict['taup_time']
pick = phase_dict['pick']
pick_time = pick.time
ttres = pick_time - taup_time
# I think doing this is wrong because taup times do NOT include station elevation!!! 5.3.20
phase_dict['ttres'] = ttres
mean = np.mean([phase_dict['pick'].time.timestamp for phase_dict in picks_list])
for phase_dict in picks_list:
phase_dict['ttres_demeaned'] = phase_dict['ttres'] - mean # see above comment
phase_dict['abstimes'] = phase_dict['pick'].time - origin_time
return picks_list
def translate_phase_names_TAUP(sorted_picks):
# PKP and PKIKP are all just 'P' phases in fm3d (not very sure about this!)
translations = {'PKIKP': 'PKP',
'SKIKS': 'SKS',}
for phase_name in sorted_picks.keys():
if phase_name in translations.keys():
sorted_picks[translations[phase_name]] = sorted_picks.pop(phase_name)
return sorted_picks
def write_picks_to_pickfile(pickfile, phase, picks_list, stations_dict, origin, picks_mode='abs'):
fid = open(pickfile, 'w')
header = '{npicks}\n' \
'{lat_src:<15} {lon_src:<15} {depth_src:<15}\n' \
'{phase_name}\n'
# pickfile header including npicks, src coords, phasetype
formatted_str = header.format(npicks=len(picks_list),
lat_src=origin.latitude,
lon_src=origin.longitude,
depth_src=origin.depth,
phase_name=phase)
fid.write(formatted_str)
# write picks for each station to pickfile
for phase_dict in picks_list:
pick = phase_dict['pick']
seed_id = pick.waveform_id.get_seed_string()
# travel time residual (demeaned or abs)
if picks_mode == 'abs':
picktime = phase_dict['abstimes']
elif picks_mode == 'res':
warnings.warn('Using residuals here might not be exact. See above code where ttres_demeaned is calculated.')
picktime = phase_dict['ttres_demeaned']
else:
raise IOError(f'Unknown picks_mode {picks_mode}')
uncertainty = pick.time_errors.uncertainty
network, station = seed_id.split('.')[:2]
# get stations coords from metadata dict
station_coords = stations_dict.get('{}.{}'.format(network, station))
# prepare line to be written to pickfile and format, use traveltime residual
line = '{lat_rec:<15} {lon_rec:<15} {depth_rec:<15} {picktime:<15} {uncert:15}\n'
formatted_line = line.format(lat_rec=station_coords['latitude'],
lon_rec=station_coords['longitude'],
depth_rec=station_coords['elevation'] * (-1e-3),
picktime=picktime,
uncert=uncertainty)
fid.write(formatted_line)
print('Wrote {} picks for phase {} to file {}'.format(len(picks_list), phase, pickfile))
fid.close()
def remove_blacklisted_station_picks(picks, fname_blacklist, verbosity=1):
blacklisted_stations = get_station_blacklist(fname_blacklist)
deleted_picks = []
deleted_stations = []
for index, pick in list(reversed(list(enumerate(picks)))):
seed_id = pick.waveform_id.get_seed_string()
network, station = seed_id.split('.')[:2]
nwst_id = '{}.{}'.format(network, station)
if nwst_id in blacklisted_stations.keys():
timewindow = blacklisted_stations[nwst_id]
if not timewindow == 'always':
tstart, tstop = [UTCDateTime(time) for time in timewindow.split('to')]
if not tstart <= picks[index].time <= tstop:
continue
deleted_picks.append(picks.pop(index))
deleted_stations.append(nwst_id)
if verbosity > 0:
print('Deleted picks for blacklisted stations:\n{}'.format(deleted_stations))
return picks, deleted_stations
def get_station_blacklist(fname_csv):
with open(fname_csv, 'r') as infile:
# skip first line
blacklist = infile.readlines()[1:]
blacklisted_stations = {}
for line in blacklist:
network, station, time = line.split(',')[:3]
nwst_id = '{}.{}'.format(network, station)
blacklisted_stations[nwst_id] = time
return blacklisted_stations
def remove_uncommon_picks(sorted_picks, min_picks_per_phase):
for phase_name, picks_list in reversed(list(sorted_picks.items())):
if len(picks_list) < min_picks_per_phase:
msg = 'Removed picks for phase {}, as there are only {} picks given (threshold is {})'
print(msg.format(phase_name, len(picks_list), min_picks_per_phase))
del(sorted_picks[phase_name])
return sorted_picks
def sort_picks_by_phase(picks, stations_dict, source_origin, phase_types, model,
max_phase_diff=50., merge_phases=None, verbosity=0):
'''
# First: Iterate through all picks to estimate possible phases for source/location combination, then assign
# each pick to one phase and return sorted picks dictionary, as there has to be one pickfile for each phasetype
:param picks:
:param stations_dict:
:param source_origin:
:param phase_types:
:param model:
:param max_phase_diff:
:param merge_phases: assign a phase (key) to another phase (value) e.g. {Pdiff: P}
:param verbosity:
:return:
'''
print('Starting to sort picks by phase calculating theoretical travel times for each pick...')
phases_dict = {}
for pick in picks:
# PROBLEM: seed_id from PyLoT does not contain Location ID!!! Makes it hard to find station coords
# workaround: use stations_dict (ignore channel and location ID) instead of metadata.get_coordinates()
seed_id = pick.waveform_id.get_seed_string()
network, station = seed_id.split('.')[:2]
phaseID = pick.phase_hint
uncertainty = pick.time_errors.uncertainty
# skip different phase
if not phaseID in phase_types:
continue
# pick invalid if no uncertainty is given
if not uncertainty:
continue
station_coords = stations_dict.get('{}.{}'.format(network, station))
if not station_coords:
print('Could not find coordinates for station: {}'.format(seed_id))
continue
# estimate phase type by taking the closest phase from Tau-P method (time is relative to source origin)
phase_name, phase_time_rel, phase_diff = get_closest_taup_phase(source_origin, station_coords,
phaseID, pick.time, model)
if phase_diff > max_phase_diff:
if verbosity > 0:
print ('Warning, max_diff too large (> {} s) for phase {} at {}. Skipping'.format(max_phase_diff,
phase_name, seed_id))
continue
phase_time = source_origin.time + phase_time_rel
# merge phase if selected, keep track of original phase for TauPy node initiation
true_phase = phase_name
if merge_phases and phase_name in merge_phases.keys():
phase_name = merge_phases[phase_name]
if not phase_name in phases_dict.keys():
print('Adding phase to sorted picks dictionary: {}'.format(phase_name))
phases_dict[phase_name] = []
phases_dict[phase_name].append({'pick': pick,
'taup_time': phase_time,
'true_phase': true_phase,})
return phases_dict
def get_closest_taup_phase(source_origin, station_coords, phase, pick_time, model='ak135'):
'''
Estimate phase that was picked by searching for earliest P/S phase arriving at
station_coords for given source_coords using TauPy. As PyLoT only searches for first arrivals
using the same method, this should(!) yield the correct phase.
:return:
'''
phases = {'P': [],
'S': []}
phase_lists = {'P': ['ttp'],
'S': ['tts']}
# possible phases for fm3d (*K* and *KIK* -> *)
#phase_list = {'P': ['P', 'PKP', 'PKiKP', 'PKIKP', 'PcP', 'ScP', 'SKP', 'PKKP', 'SKKP', 'PP',],
# 'S': ['S', 'SKS', 'SKIKS', 'ScS']}
# in case pick phase hint is just P or S
if phase in phases.keys():
phase_list = phase_lists[phase]
common_phase = phase
# in case an explicit phase name is given use only this phase
else:
phase_list = [phase]
common_phase = identifyPhaseID(phase)
model = TauPyModel(model=model)
arrivals = model.get_travel_times_geo(source_origin.depth,
source_origin.latitude,
source_origin.longitude,
station_coords['latitude'],
station_coords['longitude'],
phase_list)
# identifies phases from arrivals as P or S phase, not necessary when using 'ttp' or 'tts' for get_travel_times_geo
for arr in arrivals:
phases[identifyPhaseID(arr.phase.name)].append(arr)
source_time = source_origin.time
if not arrivals:
raise Exception('No arrivals found for source.')
# get first P or S onsets from arrivals list
arr, min_diff = min([(arr, abs(source_time + arr.time - pick_time)) for arr in phases[common_phase]],
key=lambda t: t[1])
return arr.name, arr.time, min_diff
def get_event_obspy_dmt(eventdir):
event_pkl_file = os.path.join(eventdir, 'info', 'event.pkl')
if not os.path.exists(event_pkl_file):
raise IOError('Could not find event path for event: {}'.format(eventdir))
event = qml_from_obspyDMT(event_pkl_file)
return event
def get_event_pylot(eventdir, extension=''):
event_id = get_event_id(eventdir)
filename = os.path.join(eventdir, 'PyLoT_{}{}.xml'.format(event_id, extension))
if not os.path.isfile(filename):
return
cat = read_events(filename)
return cat[0]
def get_event_id(eventdir):
event_id = os.path.split(eventdir)[-1]
return event_id
def get_picks(eventdir, extension=''):
event_id = get_event_id(eventdir)
filename = 'PyLoT_{}{}.xml'
filename = filename.format(event_id, extension)
fpath = os.path.join(eventdir, filename)
fpaths = glob.glob(fpath)
if len(fpaths) == 1:
cat = read_events(fpaths[0])
picks = cat[0].picks
return picks
elif len(fpaths) == 0:
print('get_picks: File not found: {}'.format(fpath))
return
print(f'WARNING: Ambiguous pick file specification. Found the following pick files {fpaths}\nFilemask: {fpath}')
return
def init_sources_file(nsrc, filename='sources.in'):
infile_source = open(filename, 'w')
infile_source.write('{}\n'.format(nsrc))
return infile_source
def init_receivers_file(nrec, filename='receivers.in'):
infile_rec = open(filename, 'w')
infile_rec.write('{}\n'.format(nrec))
return infile_rec
def append_source(fid, origin):
pass
def append_receiver(fid, station_coords):
# TODO check if order is correct
fid.write('{rad:15} {lat:15} {lon:15} \n'.format(rad=station_coords['elevation'],
lat=station_coords['latitude'],
lon=station_coords['longitude']))
def organize_receivers(fnin, unique=False):
''' Open FMTOMO receivers.in file and read position of each receiver into a dict.'''
with open(fnin, 'r') as infile:
nRec = int(infile.readline())
rec_dict = {}
for rec_number in range(1, nRec + 1):
rad, lat, lon = [float(value) for value in infile.readline().split()]
# dummy read next 3 lines
for ind in range(3):
infile.readline()
receiver = {'rad': rad,
'lat': lat,
'lon': lon, }
if unique:
if receiver in rec_dict.values():
continue
rec_dict[rec_number] = receiver
return rec_dict
def organize_sources(fnin):
''' Open FMTOMO sources.in file and read position and phase of each source into a dict.'''
with open(fnin, 'r') as infile:
nSrc = int(infile.readline())
src_dict = {}
for src_number in range(nSrc):
src_number += 1
teleseismic_flag = int(infile.readline())
if teleseismic_flag != 1: raise ValueError('Source not teleseismic!')
phase = infile.readline().strip()
rad, lat, lon = [float(value) for value in infile.readline().split()[:3]]
# dummy read next 4 lines
for ind in range(4):
infile.readline()
src_dict[src_number] = {'phase': phase,
'depth': rad,
'lat': lat,
'lon': lon,}
return src_dict
def organize_event_names(fnin):
infile = open(fnin, 'r')
events = [line.split()[-1].strip() for line in infile.readlines()[1:]]
infile.close()
return events
def export_otimes(otimes, fn_out):
np.savetxt(fn_out, otimes, fmt=['%8d', '%8d', '%8d', '%8d', '% 4.6f', '% 4.6f'],
header=str(len(otimes)), comments='')
print('Wrote file:', fn_out)
if __name__ == '__main__':
# testing area
prepare_fmtomo_dir_first_run('/data/AdriaArray_Data/fmtomo_adriaarray/v0/test_init')
@@ -0,0 +1,104 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# get traveltime residuals using synthetic 1D travel times from FMTOMO hybrid method
#
# this script is used to make sure that synthetic travel times are the same for synthetic data residuals (rtimes)
# and observed data residuals, because of small differences between hybrid 1D solver and taup method
# Therefore, correlation output times are absolute (minus source time) values and residuals are calcuated using
# FMTOMO hybrid reference times (e.g. rtimes.dat)
#
import os
import argparse
import shutil
import numpy as np
import matplotlib.pyplot as plt
from pylot.tomography.fmtomo_tools.fmtomo_teleseismic_utils import organize_event_names
# Note: weighting of mean might not be reasonable due to uncertainty distribution along the array
def main(infile_rtimes, fn_events, kde_file, demean=True, no_deref=False, weight_kde=True, weight=False):
eventnames = organize_event_names(fn_events)
assert not(weight and weight_kde), 'Cannot use two weights'
if not no_deref:
reftimes = np.genfromtxt(infile_rtimes)
if weight_kde:
kde_values = np.genfromtxt(kde_file)
assert len(kde_values) == len(reftimes), 'Missmatch in reftimes and kde file length'
nSrc = len(eventnames)
diff_in_means = []
# iterate over all sources
for index in range(nSrc):
srcid = index + 1
eventname = eventnames[index]
print('{} ({}/{})'.format(eventname, srcid, nSrc))
# get pick filename and read observed times
pickfile = os.path.join('picks', eventname)
obsdata_src = np.loadtxt(pickfile, skiprows=3)
# make safety copy
picksafedir = os.path.join('picks', 'save')
if not os.path.isdir(picksafedir):
os.mkdir(picksafedir)
shutil.copy(pickfile, picksafedir)
npicks = len(obsdata_src)
# read observed times from pickfile
tt_obs = obsdata_src[:, 3]
# read uncertainties from pickfile
uncs = obsdata_src[:, 4]
if no_deref:
tt_res = tt_obs
else:
indices = np.where(reftimes[:, 1] == srcid)[0]
assert (len(indices) == npicks), 'Missmatch in indices for srcids'
# read reference teleseismic times from FMTOMO run
tt_ref = reftimes[:, 4][indices]
# calculate residuals
tt_res = tt_obs - tt_ref
# set new residual to obsdata array
obsdata_src[:, 3] = tt_res
# get kde values
#weights = 1./kde_values[:, 2][indices] if weight_kde else None
#
weights = 1. / uncs ** 2 if weight else None
# demean residuals for current source
mean = np.average(tt_res, weights=weights)
mean_unweighted = np.mean(tt_res)
diff_in_means.append(mean - mean_unweighted)
print('Mean:', mean)
print('Unweighted Mean:', mean_unweighted)
print('Demean setting:', demean)
if demean:
obsdata_src[:, 3] -= mean
#obsdata_src[:, 3] = mtimes[:, 4][indices]
# Write new pickfiles
with open(pickfile, 'r') as infile_pick:
header = infile_pick.readlines()[:3]
with open(pickfile, 'w') as outfile_pick:
for line in header:
outfile_pick.write(line)
for line in obsdata_src:
outfile_pick.write('{:10.4f} {:10.4f} {:10.4f} {:15.8f} {:15.8f}\n'.format(*line))
plt.hist(diff_in_means, bins=100)
plt.title('Diffs in means (weighted - unweighted) [s]')
plt.show()
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Calculate residuals for absolute observation times using reference file from FMTOMO')
parser.add_argument('--rtimes', default='rtimes_tele.dat', help='reference_file')
parser.add_argument('--sourcefile', default='input_source_file_P.in', help='input_source_file')
#parser.add_argument('--kdefile', default='kde_weights.txt', help='input file with station kde values for weighting')
parser.add_argument('-nd', '--no_demean', action='store_true', default=False,
help='do not mean correct travel times')
parser.add_argument('-nr', '--no_reftimes', action='store_true', default=False,
help='do not use reference times (e.g. picks are already tt residuals). rtimes file still required at the moment.')
#parser.add_argument('-nwk', '--no_weight_kde', action='store_true', default=True, help='do not weight travel times')
args = parser.parse_args()
#args.kdefile
main(args.rtimes, args.sourcefile, None, not(args.no_demean), weight_kde=False, no_deref=args.no_reftimes) #not(args.no_weight))
@@ -0,0 +1,174 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import os
import numpy as np
from obspy.geodetics import gps2dist_azimuth, locations2degrees
from pylot.tomography.utils import get_coordinate_from_dist_baz
R=6371.
def main():
profiles_infile = '/home/marcel/AlpArray/vtk_files/points_mark.txt'
gmtslice_dir = '/rscratch/minos13/marcel/fmtomo_alparray/v3.5/alparray_mantle_diehl_crust_included_hf_gradient_smoothing/plot'
####
# maximum point spacing in km (decreases with depth)
dpoints = 1.
dpi = 100
model_depth = 606.
###
with open(profiles_infile, 'r') as infile:
profiles = infile.readlines()
for profile in profiles:
print('Profile: ', profile)
name = profile.split()[0]
lon1, lat1, lon2, lat2 = [float(item) for item in profile.split()[1:]]
def get_gmtslice_gc(gmtplot_dir, lat1, lon1, lat2, lon2, model_depth=606., dpoints=1, fn_out_slice='grid2dvgc.z',
rel=True, fname_vgrid='../vgrids.in', fname_vgrid_ref='../vgridsref.in'):
parfile_str_len = 28
assert (len(fname_vgrid) < parfile_str_len), 'Check length of filename: {}'.format(fname_vgrid)
assert (len(fname_vgrid_ref) < parfile_str_len), 'Check length of filename: {}'.format(fname_vgrid_ref)
infile_default = 'gmtslice_default.in'
fn_out = 'gmtslice.in'
bounds_out_gmt = 'boundgc.gmt'
cwd = os.getcwd()
os.chdir(gmtplot_dir)
with open(infile_default, 'r') as infile:
gmt_infile = infile.readlines()
# calculate great circle distance
max_dist = gps2dist_azimuth(lat1, lon1, lat2, lon2, a=R * 1e3, f=0)[0]/1e3
npointsR = int(model_depth / dpoints + 1)
npointsLateral = int(max_dist / dpoints + 1)
print('nR, nLateral:', npointsR, npointsLateral)
# filename
gmt_infile[3] = '{} \n'.format(fname_vgrid)
gmt_infile[4] = '{} \n'.format(fname_vgrid_ref)
# activate gc generation
gmt_infile[51] = '1 \n'
# modify lines with lat lon boundaries
gmt_infile[52] = '{:5.2f} {:5.2f}\n'.format(lat1, lon1)
gmt_infile[53] = '{:5.2f} {:5.2f}\n'.format(lat2, lon2)
abs_rel = 1 if rel else 0
gmt_infile[58] = '{} \n'.format(abs_rel)
# modify lines with n Points
gmt_infile[65] = '{:<5d} {:<5d}\n'.format(npointsLateral, npointsR)
with open(fn_out, 'w') as outfile:
for line in gmt_infile:
outfile.write(line)
print('Executing gmtslice...')
os.system('gmtslice')
print('Done!')
with open(bounds_out_gmt, 'r') as infile:
bds = [float(bd) for bd in infile.readlines()]
bounds = '-R{bds[0]}/{bds[1]}/{bds[2]}/{bds[3]}'.format(bds=bds)
xyz_string = 'gmt xyz2grd {grid_out} -Ggrid2dvgc.grd -I{bds[4]}+/{bds[5]}+ -ZLB {bounds}'.format(
grid_out=fn_out_slice, bds=bds, bounds=bounds)
print(xyz_string)
os.system(xyz_string)
grid = np.loadtxt(fn_out_slice)
dist_grid = []
lon_grid = []
lat_grid = []
_, azim, bazim = gps2dist_azimuth(lat1, lon1, lat2, lon2, a=R * 1e3, f=0)
#ddist = / (npointsLateral - 1)
#ddepth = (bds[3] - bds[2]) / (npointsR - 1)
for depth in np.linspace(-bds[3], -bds[2], num=npointsR):
for dist in np.linspace(0, locations2degrees(lat1, lon1, lat2, lon2), num=npointsLateral):
lon, lat = get_coordinate_from_dist_baz((lon1, lat1), dist, azim)
lon_grid.append(lon)
lat_grid.append(lat)
#dist = ddist * indexLat
#depth = ddepth * indexR
dist_grid.append(np.array([dist, depth]))
dist_grid = np.array(dist_grid)
lat_grid = np.array(lat_grid)
lon_grid = np.array(lon_grid)
os.chdir(cwd)
return grid, dist_grid, lat_grid, lon_grid, max_dist
def get_gmtslice_depth(gmtplot_dir, depth, fn_out_slice='grid2dvd.z', fname_vgrid='../vgrids.in',
fname_vgrid_ref='../vgridsref.in'):
infile_default = 'gmtslice_default.in'
fn_out = 'gmtslice.in'
bounds_out_gmt = 'bounddp.gmt'
cwd = os.getcwd()
os.chdir(gmtplot_dir)
with open(infile_default, 'r') as infile:
gmt_infile = infile.readlines()
# filename
gmt_infile[3] = '{} \n'.format(fname_vgrid)
gmt_infile[4] = '{} \n'.format(fname_vgrid_ref)
# activate depth slice generation
gmt_infile[41] = '1 \n'
# set depth (negative for whatever reason)
gmt_infile[42] = '{:5.2f} \n'.format(-depth)
with open(fn_out, 'w') as outfile:
for line in gmt_infile:
outfile.write(line)
print('Executing gmtslice...')
os.system('gmtslice')
print('Done!')
with open(bounds_out_gmt, 'r') as infile:
bds = [float(bd) for bd in infile.readlines()]
bounds = '-R{bds[0]}/{bds[1]}/{bds[2]}/{bds[3]}'.format(bds=bds)
xyz_string = 'gmt xyz2grd {grid_out} -Ggrid2dvd.grd -I{bds[4]}+/{bds[5]}+ -ZLB {bounds}'.format(
grid_out=fn_out_slice, bds=bds, bounds=bounds)
print(xyz_string)
#os.system(xyz_string)
grid = np.loadtxt(fn_out_slice)
lonlat = []
lon0, lon1 = bds[:2]
lat0, lat1 = bds[2:4]
nlon = int(bds[4])
nlat = int(bds[5])
for lon in np.linspace(lon0, lon1, num=nlon):
for lat in np.linspace(lat0, lat1, num=nlat):
lonlat.append(np.array([lon, lat]))
lonlat = np.array(lonlat)
os.chdir(cwd)
return grid, lonlat
def grdimage_slice(lat1, lon1, lat2, lon2, bounds, name, npointsLateral, npointsR, dpi):
proj = "-JX{:05.2f}i/{:05.2f}i".format(npointsLateral / dpi, npointsR / dpi)
fnout = 'gmtslice_{}_{:.1f}_{:.1f}-{:.1f}_{:.1f}'.format(name, lat1, lon1, lat2, lon2)
grdimage_string = 'gmt grdimage grid2dvgc.grd {bounds} {proj} -Ba50f10/a50f10 -Cpolar_inv_3.cpt -K > {fnout}'.format(
bounds=bounds, proj=proj, fnout=fnout+'.ps')
print(grdimage_string)
os.system(grdimage_string)
print('Convert to png...')
os.system('convert {} {}'.format(fnout + '.ps', fnout + '.png'))
@@ -0,0 +1,43 @@
import sys
fnin1 = '/data/AlpArray_Data/fmtomo/v6/crust_incl_hf_sm_FIX_DTS_grad_sm30_dm10/it_12/vgrids.in'
fnin2 = '/data/AlpArray_Data/fmtomo/v6/crust_incl_hf_sm_FIX_TESAURO_grad_sm30_dm10/it_12/vgrids.in'
fnout = '/data/AlpArray_Data/fmtomo/v6/crust_incl_hf_sm_FIX_DTS_grad_sm30_dm10/vg_TES_i12sm30dm10.in'
def check_vgrids_header_line(l1, l2, epsilon=1e-6):
items1 = [float(item) for item in l1.split()]
items2 = [float(item) for item in l2.split()]
for i1, i2 in zip(items1, items2):
if not abs(i1 - i2) < epsilon:
return False
return True
def vgrids_diff(fnin1, fnin2, fnout):
diffs = []
with open(fnin1, 'r') as infile1:
with open(fnin2, 'r') as infile2:
with open(fnout, 'w') as outfile:
for index, l1 in enumerate(infile1):
l2 = infile2.readline()
if index < 4:
assert check_vgrids_header_line(l1, l2), 'Different grid dimensions!'
outfile.write(l1)
else:
try:
v1 = float(l1.split()[0])
v2 = float(l2.split()[0])
except Exception as e:
print('Read problem: {}'.format(e))
sys.exit()
vdiff = abs(v2 - v1)
diffs.append(vdiff)
outfile.write('{}\n'.format(vdiff))
print('Finished writing file', fnout)
print('Max diff:', max(diffs))
vgrids_diff(fnin1, fnin2, fnout)
@@ -0,0 +1,126 @@
import os
import json
import numpy as np
import matplotlib.pyplot as plt
from pylot.tomography.fmtomo_tools.fmtomo_teleseismic_utils import organize_receivers
# TODO: demeaning source by source??
def calc_misfit(data, synth, unc):
return np.sum(((data - synth) / unc) ** 2) / len(data)
def calc_mean_residuals(data, synth):
return np.sum(abs(data - synth)) / len(data)
def sort_stations_for_search(station_coords, receivers_dict):
epsilon = 1e-1 # very large epsilon actually...
coords_unique = coords_unique_receivers(receivers_dict)
latlon_dict = {}
for nwst_id, coords in station_coords.items():
lat = coords['latitude']
lon = coords['longitude']
for latu, lonu in coords_unique:
if abs(lat - latu) < epsilon and abs(lon - lonu) < epsilon:
latlon_dict[(latu, lonu)] = nwst_id
return latlon_dict
def extract_stations_for_network(station_coords, network_id):
station_coords_extracted = {}
for nwst_id, coords in station_coords.items():
nw, st = nwst_id.split('.')
if nw == network_id:
station_coords_extracted[nwst_id] = coords
return station_coords_extracted
def get_receiver_ids(latlon_list, receivers_dict):
receiver_ids = []
for recid, coords in receivers_dict.items():
lat, lon = (coords['lat'], coords['lon'])
if (lat, lon) in latlon_list:
receiver_ids.append(recid)
return receiver_ids
def coords_unique_receivers(receivers_dict):
coords_unique = []
for coords in receivers_dict.values():
latlon_tuple = (coords['lat'], coords['lon'])
if not latlon_tuple in coords_unique:
coords_unique.append(latlon_tuple)
return coords_unique
#def plot_residuals()
def main():
#wdir = '/rscratch/minos13/marcel/fmtomo_alparray/alparray_mantle_diehl_crust_included_v3_hf'
wdir = '/data/AlpArray_Data/fmtomo/v5/crust_incl_hf_sm_FIX_DTS_grad_sm30_dm10'
os.chdir(wdir)
receivers_dict = organize_receivers('receivers.in')
print(wdir)
misfits = {}
mean_residuals = {}
for extract_network in [None, 'Z3', 'ZS', ]:
misfits[extract_network] = []
mean_residuals[extract_network] = []
with open('/rscratch/minos13/marcel/alparray/station_coords.json', 'r') as infile:
station_coords = json.load(infile)
print('\nCalculating residuals for network:', extract_network)
if extract_network:
station_coords = extract_stations_for_network(station_coords, extract_network)
latlon_dict = sort_stations_for_search(station_coords, receivers_dict)
rec_ids = get_receiver_ids(list(latlon_dict.keys()), receivers_dict)
otimes = np.genfromtxt('otimes.dat', skip_header=1)
#otimes_orig = np.genfromtxt('otimes_orig.dat', skip_header=1)
ttimes = otimes[:, 4]
uncs = otimes[:, 5]
nsrc = int(otimes[-1, 1])
rtimes = np.genfromtxt('rtimes_tele.dat')[:, 4]
for itstep in range(0, 25):
nRays = 0
mtimes_all = np.genfromtxt('it_{}/arrivals.dat'.format(itstep))
mtimes = mtimes_all[:, 4]
mtimes_diff = mtimes - rtimes
for srcid in range(nsrc):
srcid += 1
# get all indices of current source id
indices = np.where(mtimes_all[:, 1] == srcid)
nRays += len(indices[0])
mtimes_diff[indices] -= np.mean(mtimes_diff[indices])
# get all indices that are in rec_ids list as well
mask_recs = np.isin(mtimes_all[:, 0].astype(int), rec_ids)
mf = calc_misfit(ttimes[mask_recs], mtimes_diff[mask_recs], uncs[mask_recs])
sr = calc_mean_residuals(ttimes[mask_recs], mtimes_diff[mask_recs])
misfits[extract_network].append(mf)
mean_residuals[extract_network].append(sr)
print('It: {}, misfit: {}, mean res: {}, nrays: {}'.format(itstep, mf, sr, nRays))
#plt.plot(mean_residuals[extract_network], label=extract_network)
plt.plot(misfits[extract_network], label=extract_network)
plt.title(wdir)
plt.ylabel('Mean residuals [s]')
plt.xlabel('Iteration')
plt.legend()
plt.show()
if __name__ == '__main__':
main()
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import glob, os, shutil
from fmtomo_tools.fmtomo_grid_utils import read_vgrid, write_vgrid
pjoin = os.path.join
pwd = '/data/AlpArray_Data/fmtomo/v6/crust_incl_hf_sm_FIX_DTS_grad_sm30_dm10_EASI_test_Plomerova/'
vgrid_file_in = pjoin(pwd, 'vgrids_dts_crust.in')
vgrid_file_out = pjoin(pwd, 'vgrids_dts_crust_variance_slice.in')
latmin_rec, latmax_rec = 44.8, 51.3
lonmin_rec, lonmax_rec = 11.94, 14.66
latmin_grid, latmax_grid = 44.45, 52.55
lonmin_grid, lonmax_grid = 10.83, 15.77
def modify_pickfiles():
picks_path = pjoin(pwd, 'picks_orig')
os.chdir(picks_path)
outdir = pjoin(pwd, 'picks')
infiles = glob.glob('*.ttf')
for infile in infiles:
lines_out = []
with open(infile, 'r') as fid:
eventid = infile.split('.ttf')[0]
npicks = int(fid.readline())
# copy source/phase header
for _ in range(2):
lines_out.append(fid.readline())
for line in fid:
lat, lon = [float(item) for item in line.split()[:2]]
if latmin_rec < lat < latmax_rec and lonmin_rec < lon < lonmax_rec:
lines_out.append(line)
fn_out = pjoin(outdir, infile)
with open(fn_out, 'w') as outfile:
# number of picks: list contents - 2 header lines
outfile.write(f'{len(lines_out) - 2}\n')
for line in lines_out:
outfile.write(line)
print(f'Modified {eventid}: Removed {npicks - len(lines_out)} out of {npicks} picks/stations')
os.chdir(pwd)
def modify_variance_slice():
grid, gridN, gridDelta, gridStart = read_vgrid(vgrid_file_in)
for index, latlon in enumerate(zip(grid['lats'], grid['lons'])):
lat, lon = latlon
if not latmin_grid < lat < latmax_grid or not lonmin_grid < lon < lonmax_grid:
grid['covs'][index] = 1e-6
write_vgrid(grid, gridN, gridDelta, gridStart, vgrid_file_out)
#modify_pickfiles()
modify_variance_slice()
@@ -0,0 +1,294 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import numpy as np
from scipy.interpolate import RegularGridInterpolator
import matplotlib.pyplot as plt
from pylot.tomography.fmtomo_tools.fmtomo_teleseismic_utils import export_otimes, organize_receivers
def crustal_correction_using_differences(fname_otimes, fname_otimes_diff, fname_otimes_out):
otimes = np.loadtxt(fname_otimes, skiprows=1)
otimes_diff = np.loadtxt(fname_otimes_diff, skiprows=1)
src_means = source_means(otimes_diff)
#mean_diffs = np.mean([float(item.split()[4]) for item in otimes_diff[1:]])
print('Mean_diffs of all sources: ', np.mean(list(src_means.values())))
# TODO: Check if demeaning is useful for crustal correction using synthetics (was it b4 using ak135_diehl 1D?)
# Update 27.5.2020: If not demeaned, overall velocity perturbation shifted to negative in whole upper mantle
# BUT: demean has to be executed for each source, not with global mean!
with open(fname_otimes_out, 'w') as outfile:
outfile.write('{}\n'.format(len(otimes)))
for index, line in enumerate(otimes):
src_id = line[1]
ttime = line[4]
ttime_diff = otimes_diff[index][4]
new_time = ttime - ttime_diff + src_means[src_id]
line[4] = new_time
for col_index, item in enumerate(line):
if col_index < 4:
fmt = '{:10g} '
else:
fmt = '{:10.8f} '
outfile.write(fmt.format(item))
outfile.write('\n')
def source_means(otimes):
# catch all source ids from otimes file
src_ids = np.unique(otimes[:, 1])
print('Source IDs:', src_ids)
# create dictionary containing means of travel time (differences) for each source (key)
src_means = {src_id: np.mean(otimes[otimes[:, 1] == src_id][:, 4]) for src_id in src_ids}
print('Source means:')
for srcid, val in src_means.items():
print(srcid, ': ', val)
return src_means
def crustal_correction_using_residual_map(fname_otimes, fname_residuals, receivers_in, fname_otimes_out):
''' correct using residuals from numpy file with an array of shape (lons, lats, residuals)'''
otimes = np.loadtxt(fname_otimes, skiprows=1)
rec_dict = organize_receivers(receivers_in)
lons, lats, res = np.load(fname_residuals, allow_pickle=True)
lonsU = np.unique(lons)
latsU = np.unique(lats)
#grid = (lonsU, latsU)
#shape = [len(item) for item in grid]
#res = res.reshape(shape)
# not very efficient but very lazy (just interpolate value for each ray, i.e. receiver, not unique,
# which is quite redundant)
rginter = RegularGridInterpolator((lonsU, latsU), res, bounds_error=False, fill_value=0.)
test_rginter(rginter)
for line in otimes:
rec_id = int(line[0])
lonlat = (rec_dict[rec_id]['lon'], rec_dict[rec_id]['lat'])
res = rginter(lonlat)
line[4] -= res
src_means = source_means(otimes)
for src_id, src_mean in src_means.items():
otimes[otimes[:, 1] == src_id, 4] -= src_mean
with open(fname_otimes_out, 'w') as outfile:
outfile.write('{}\n'.format(len(otimes)))
for line in otimes:
for col_index, item in enumerate(line):
if col_index < 4:
fmt = '{:10g} '
else:
fmt = '{:10.8f} '
outfile.write(fmt.format(item))
outfile.write('\n')
def test_rginter(rginter):
lons = np.linspace(0, 22, 220)
lats = np.linspace(40, 52, 120)
lonsg, latsg = np.meshgrid(lons, lats)
data = rginter((lonsg, latsg))
pcm = plt.pcolormesh(lonsg, latsg, data)
plt.colorbar(pcm)
plt.show()
def get_otimes_diff(fname_arrivals1, fname_arrivals2, fname_out='otimes_diff.dat'):
'''
Calculate file containing travel time differences between fname_arrivals1 and fname_arrivals2, e.g. for crustal
correction.
:param fname_arrivals1:
:param fname_arrivals2:
:return:
'''
with open(fname_arrivals1, 'r') as infile:
arrivals1 = infile.readlines()
with open(fname_arrivals2, 'r') as infile:
arrivals2 = infile.readlines()
assert len(arrivals1) == len(arrivals2), 'Length of input arrival files differs'
print('Calculating differences between file {} and {}'.format(fname_arrivals1, fname_arrivals2))
columnString = '{} '
nArrivals = len(arrivals1)
with open(fname_out, 'w') as outfile:
outfile.write('{}\n'.format(nArrivals))
for index in range(nArrivals):
line1 = arrivals1[index]
line2 = arrivals2[index]
for item in line1.split()[:4]:
outfile.write(columnString.format(item))
diff = float(line1.split()[4]) - float(line2.split()[4])
outfile.write(columnString.format(diff))
outfile.write('\n')
print('Wrote {} lines to file {}'.format(nArrivals, fname_out))
def get_synthetic_obsdata_legacy(fname_m1, fname_m2, fname_otimes_orig, fname_out='otimes_modif.dat'):#, p_err=0.1):
'''
Create synthetic obsdata of model 1 relative to model 2 (usually 1D model synthetic travel times)
:param fname_m1: arrivals.dat file of synthetic travel times (e.g. from checkerboard test)
:param fname_m2: arrivals.dat file of synthetic travel times (e.g. ak135 model)
:param fname_otimes_orig: original otimes file for pick uncertainties
:param fname_out: otimes.dat file with output
:param p_err: picking error
:return:
'''
with open(fname_m1, 'r') as infile:
arrivals_model1 = infile.readlines()
with open(fname_m2, 'r') as infile:
arrivals_model2 = infile.readlines()
with open(fname_otimes_orig, 'r') as infile:
otimes = infile.readlines()[1:]
assert(len(arrivals_model1) == len(arrivals_model2) == len(otimes)), 'missmatch in lengths of files!'
diffs = []
with open(fname_out, 'w') as outfile:
outfile.write('{}\n'.format(len(arrivals_model1)))
for line_model1, line_model2, line_otimes in zip(arrivals_model1, arrivals_model2, otimes):
for item in line_model2.split()[:4]:
outfile.write('{} '.format(item))
ttime_model1 = float(line_model1.split()[4])
ttime_model2 = float(line_model2.split()[4])
pickerror = float(line_otimes.split()[5])
# pickerror = (p_err+2*p_err*abs(np.random.randn()))
ttime_diff = ttime_model1 - ttime_model2
diffs.append(ttime_diff)
outfile.write('{} {}\n'.format(ttime_diff, pickerror))
mean_diff = np.mean(diffs)
abs_mean_diff = np.mean(np.abs(diffs))
print('Mean_diff: {} - abs_mean_diff: {}'.format(mean_diff, abs_mean_diff))
print('Done with {} travel times. Output in file: {}'.format(len(arrivals_model1), fname_out))
def get_synthetic_obsdata(fname_m1, fname_m2, fname_otimes_orig, fname_out='otimes_modif.dat', sigma='original'):
'''
Create synthetic obsdata of model 1 relative to model 2 (usually 1D model synthetic travel times)
:param fname_m1: arrivals.dat file of synthetic travel times (e.g. from checkerboard test)
:param fname_m2: arrivals.dat file of synthetic travel times (e.g. ak135 model)
:param fname_otimes_orig: original otimes file for pick uncertainties
:param fname_out: otimes.dat file with output
:param p_err: picking error
:return:
'''
arrivals_model1 = np.genfromtxt(fname_m1)
arrivals_model2 = np.genfromtxt(fname_m2)
otimes = np.genfromtxt(fname_otimes_orig, skip_header=1)
assert(len(arrivals_model1) == len(arrivals_model2) == len(otimes)), 'missmatch in lengths of files!'
# get new array as copy from model1 after deleting last column (not needed for otimes)
arrivals_diff = np.delete(arrivals_model1, 6, 1)
# overwrite time column by diffs
arrivals_diff[:, 4] -= arrivals_model2[:, 4]
# overwrite last column by pick errors
arrivals_diff[:, 5] = otimes[:, 5]
# get max nSrc (in last line of sorted arrivals files)
nSrc = int(arrivals_diff[-1, 1])
print('N sources:', nSrc)
if sigma not in [None, False]:
print('Applying gaussian noise with sigma={}'.format(sigma))
if sigma == 'original':
sigma_val = otimes[:, 5]
else:
sigma_val = sigma
gauss_noise = np.random.normal(0., sigma_val, len(arrivals_diff[:, 4]))
plt.axhline(0., color='grey')
plt.vlines(np.arange(0, len(arrivals_diff)), arrivals_diff[:, 4], arrivals_diff[:, 4] + gauss_noise,
color='grey', linestyles='dashed')
plt.plot(arrivals_diff[:, 4], 'r.')
arrivals_diff[:, 4] += gauss_noise
for index in range(nSrc):
src_id = index + 1
# get indices for this source ID
indices = np.where(arrivals_diff[:, 1] == src_id)
# get mean for that srcid
mean = np.mean(arrivals_diff[:, 4][indices])
print('Srcid: {}, mean: {}'.format(src_id, mean))
# de-mean array
arrivals_diff[:, 4][indices] -= mean
if sigma not in [None, False]:
plt.axhline(0., color='grey')
#plt.vlines(np.arange(0, len(arrivals_diff)), arrivals_diff[:, 4], arrivals_diff[:, 4] + gauss_noise)
plt.plot(arrivals_diff[:, 4], 'b.')
plt.figure()
plt.hist(gauss_noise, bins=100)
plt.show()
export_otimes(arrivals_diff, fname_out)
def plot_histograms_for_crustal_corrections():
import matplotlib.pyplot as plt
ttdiffs_no_crust = []
ttdiffs_crust = []
ttdiffs_corrected = []
#get_synthetic_obsdata('it_synth_forward/arrivals.dat', 'arrivals_ak135_diehl.dat', 'otimes_three_boxes_no_crust.dat')
#get_synthetic_obsdata('it_synth_forward_with_crust/arrivals.dat', 'arrivals_ak135_diehl.dat', 'otimes_three_boxes_with_diehl_crust.dat')
with open('otimes_three_boxes_with_diehl_crust.dat', 'r') as infile:
lines_crust = infile.readlines()[1:]
with open('otimes_three_boxes_no_crust.dat', 'r') as infile:
lines_no_crust = infile.readlines()[1:]
with open('otimes_three_boxes_crustal_corrected.dat', 'r') as infile:
lines_corrected = infile.readlines()[1:]
for line in lines_crust:
ttdiffs_crust.append(float(line.split()[4]))
for line in lines_no_crust:
ttdiffs_no_crust.append(float(line.split()[4]))
for line in lines_corrected:
ttdiffs_corrected.append(float(line.split()[4]))
plt.hist(ttdiffs_crust, bins=100, label='tt-diff including crustal structure.')
plt.hist(ttdiffs_no_crust, bins=100, label='tt-diff without crustal structure')
plt.hist(ttdiffs_corrected, bins=100, label='tt-diff after correcting for crustal structure')
plt.xlabel('Travel time differences to ak135_diehl 1D model [s]')
plt.legend()
if __name__ == '__main__':
#wdir = '/rscratch/minos13/marcel/fmtomo_alparray/alparray_mantle_diehl_crustal_corrections_v3_hf'
#fname1 = os.path.join(wdir, 'arrivals_ak135di_crust.dat')
#fname2 = os.path.join(wdir, 'arrivals_ak135di.dat')
#get_otimes_diff(fname1, fname2, os.path.join(wdir, 'otimes_diff.dat'))
#get_synthetic_obsdata('arrivals_cb_N2.dat', 'arrivals_ak135.dat', 'otimes_cb_N2.dat')
#os.chdir('/data/AlpArray_Data/fmtomo/v4/different_test_runs/alparray_mantle_waldhauser_crust_corrected_covs_hf_gradient_smoothing')
#os.chdir('/data/AlpArray_Data/fmtomo/v4/different_test_runs/alparray_mantle_waldhauser_crust_corrected_covs_hf_gradient_smoothing')
#crustal_correction_using_residual_map('otimes.dat', 'wh_residuals.npy', 'receivers.in', 'otimes_corr_wh.dat')
#os.chdir('/data/AlpArray_Data/fmtomo/v4/different_test_runs/alparray_mantle_diehl_crust_corrected_residuals_stacked_gradient_smoothing')
#crustal_correction_using_residual_map('otimes.dat', 'diehl_2009_residuals.npy', 'receivers.in', 'otimes_corr_diehl.dat')
#os.chdir('/data/AlpArray_Data/fmtomo/v6/crust_corrected_VERTICAL_hf_sm_FIX_DTS_grad_sm30_dm10')
#crustal_correction_using_residual_map('otimes.dat', 'dts_filt_12.5_12.5_7.5_CRUST_ONLY_RESIDUALS.npy', 'receivers.in', 'otimes_corr_dts.dat')
os.chdir('/data/AlpArray_Data/fmtomo/v6/crust_corrected_WH_hf_sm_FIX_DTS_grad_sm30_dm10')
crustal_correction_using_residual_map('otimes.dat', 'wh_residuals_SORTED.npy', 'receivers.in', 'otimes_corr_wh.dat')
#crustal_correction_using_residual_map('otimes.dat', 'wh_residuals.npy', 'receivers.in', 'otimes_corr_wh.dat')
@@ -0,0 +1,154 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Different functions to modify FMTOMO velocity(inversion) grid
import argparse
import numpy as np
from math import erf
from pylot.tomography.fmtomo_tools.fmtomo_grid_utils import read_vgrid, write_vgrid, write_vtk
def main(fname_in, fname_out, fname_vtk_out=None, cov_perc=0.15, bounds=[40, 60]):
vgrid, gridN, gridDelta, gridStart = read_vgrid(fname_in)
print('Modifying covariances...')
vgrid['covs'] = []
for depth, vp in zip(vgrid['depths'], vgrid['vps']):
vgrid['covs'].append(covariance_for_depth(depth, vp, cov_perc, bounds))
write_vgrid(vgrid, npts=gridN, delta=gridDelta, start=gridStart, fname=fname_out)
if fname_vtk_out:
write_vtk(vgrid, fname_vtk_out, write_data=['vps', 'covs'])
def modify_vgrid_box(vgrid, vgrid_key, min_lon, max_lon, min_lat, max_lat, min_depth, max_depth,
val_center, val_border, scale_factor, use_erf=True, extend_to_top=False):
'''
Modify a value (vgrid_key) of vgrids.in using an error function. Value will be increase smoothly to the borders
of the external model. This function was used to prevent changes of the inversion result in the region of the
initial crustal model that was corrected for. Value smoothly increases at the borders of the initial model.
'''
# get central points of external model (here value will be minimal)
c_lon = 0.5 * (min_lon + max_lon)
c_lat = 0.5 * (min_lat + max_lat)
c_depth = 0.5 * (min_depth + max_depth)
# get half the extent of the model dimensions for function scaling
dlon = 0.5 * (max_lon - min_lon)
dlat = 0.5 * (max_lat - min_lat)
ddepth = 0.5 * (max_depth - min_depth)
print('Modifying {}...'.format(vgrid_key))
print('Center {key}: {val_c}, border {key}: {val_b}'.format(key=vgrid_key, val_c=val_center, val_b=val_border))
if not vgrid_key in vgrid.keys() or not vgrid[vgrid_key]:
vgrid[vgrid_key] = list(np.ones(len(vgrid['depths'])))
for index, tup in enumerate(zip(vgrid['lons'], vgrid['lats'], vgrid['depths'])):
lon, lat, depth = tup
if (min_lon <= lon <= max_lon and min_lat <= lat <= max_lat and depth <= max_depth):
x = abs(lon - c_lon) / dlon
y = abs(lat - c_lat) / dlat
if extend_to_top and depth < c_depth:
z = 0
else:
z = abs(depth - c_depth) / ddepth
if use_erf:
point_val = smoothing_erf(x, y, z, val_center, val_border, scale_factor=scale_factor)
else:
point_val = val_center
else:
point_val = val_border
vgrid[vgrid_key][index] *= point_val
return vgrid
def modify_vgrid_gradient(vgrid, vgrid_key, val_top, val_bot):
'''
Modify a value (vgrid_key) of vgrids.in using a linear gradient from val_top to val_bot
'''
print('Modifying {} with linear gradient...'.format(vgrid_key))
print('Top: {}, bot: {}'.format(val_top, val_bot))
if not vgrid_key in vgrid.keys():
vgrid[vgrid_key] = list(np.ones(len(vgrid['depths'])))
depth_min = min(vgrid['depths'])
depth_max = max(vgrid['depths'])
for index, tup in enumerate(zip(vgrid['lons'], vgrid['lats'], vgrid['depths'])):
lon, lat, depth = tup
vgrid[vgrid_key][index] *= linear_gradient(depth, depth_min=depth_min, depth_max=depth_max, val_top=val_top,
val_bot=val_bot)
return vgrid
def linear_gradient(depth, depth_min, depth_max, val_top, val_bot):
return (val_bot - val_top) * depth / (depth_max - depth_min) + val_top
def smoothing_erf(x, y, z, val_border, val_center, scale_factor=1.):
a = val_center
b = val_border
sx = sy = sz = scale_factor
f = (a - b) * (1. / 3. * (erf(2. * sx * (x - 1)) + erf(2. * sy * (y - 1)) + erf(2. * sz * (z - 1))) + 1.) + b
return f
def covariance_for_depth(depth, vp, cov_perc, bounds):
'''
Function that returns covariance values for certain depths, written to give low variance to crust, intermediate
to an interlayer and high variance to everything else.
:param depth: depth in kilometers
:return: covariance
'''
if depth <= bounds[0]:
return 0.05
elif bounds[0] < depth <= bounds[1]:
return 0.5 * cov_perc * vp
else:
return cov_perc * vp
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Prepare grid for fm3d teleseismic hybrid calculation.')
parser.add_argument('fname_in', help='input filename (vgrids.in)')
parser.add_argument('fname_out', help='output filename (vgrids_new.in)')
parser.add_argument('--fname_out_vtk', default=None, help='vtk output filename')
parser.add_argument('--cov_border', default=1.0, help='covariance for the model outside crustal model boundaries')
parser.add_argument('--cov_center', default=0.05, help='maximum covariance in the center of the crustal model')
parser.add_argument('--smooth_border', default=1., help='smoothing factor for the rest of the box')
parser.add_argument('--smooth_center', default=0.5, help='smoothing factor inside SWATH-D box')
args = parser.parse_args()
vgrid, gridN, gridDelta, gridStart = read_vgrid(args.fname_in)
#main(args.fname_in, args.fname_out, args.fname_out_vtk)
#vgrid = modify_vgrid_gradient(vgrid, 'smoothfactors', 1.0, 2.0)
# bounds for waldhauser residual corrections
wh_bounds = dict(lon0 = 3.23, lon1 = 13.96, lat0 = 43.18, lat1 = 49.45)
vgrid = modify_vgrid_box(vgrid, vgrid_key='covs', min_lon=wh_bounds['lon0'], max_lon=wh_bounds['lon1'],
min_lat=wh_bounds['lat0'], max_lat=wh_bounds['lat1'], min_depth=-5.0, max_depth=60.0,
val_center=float(args.cov_center), val_border=float(args.cov_border),
scale_factor=3, use_erf=True, extend_to_top=True)
#vgrid = modify_vgrid_box(vgrid, vgrid_key='covs', min_lon=2.2547, max_lon=17.0999, min_lat=41.0517,
# max_lat=50.4984, min_depth=-5.0, max_depth=70.0, val_center=float(args.cov_center),
# val_border=float(args.cov_border), scale_factor=3, use_erf=True, extend_to_top=True)
#vgrid = modify_vgrid_box(vgrid, vgrid_key='smoothfactors', min_lon=8.9, max_lon=15.3, min_lat=45.,
# max_lat=48., min_depth=-15.0, max_depth=610.0, val_center=float(args.smooth_center),
# val_border=float(args.smooth_border), scale_factor=1., use_erf=True)
# MP MP TEST TEST TEST ++++++++++++++++++++++++++++++++++++++++++++++++
#grid = modify_vgrid_box(grid, vgrid_key='smoothfactors', min_lon=11., max_lon=35., min_lat=30.,
# max_lat=61., min_depth=-5.0, max_depth=600.0, val_center=float(args.smooth_center),
# val_border=float(args.smooth_border), scale_factor=.4)
# MP MP TEST TEST TEST ------------------------------------------
write_vgrid(vgrid, npts=gridN, delta=gridDelta, start=gridStart, fname=args.fname_out)
if args.fname_out_vtk:
write_vtk(vgrid, args.fname_out_vtk, write_data=['vps', 'covs', 'smoothfactors'])
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import matplotlib.pyplot as plt
def plot_otimes(otimes_fname):
with open(otimes_fname, 'r') as infile:
input_list = infile.readlines()[1:]
ray_id = [int(line.split()[0]) for line in input_list]
ttimes = [float(line.split()[-2]) for line in input_list]
uncertainties = [float(line.split()[-1]) for line in input_list]
plt.axhline(0, linestyle=':', color='k')
plt.errorbar(ray_id, ttimes, yerr=uncertainties, fmt='o', markersize=1, ecolor='0.5', elinewidth=0.5)
plt.show()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Plot ttimes with errors of FMTOMO file otimes.dat')
parser.add_argument('infile', help='inputfile')
args = parser.parse_args()
plot_otimes(args.infile)
+341
View File
@@ -0,0 +1,341 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import argparse
import json
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
from obspy.geodetics.base import gps2dist_azimuth
import cartopy.crs as ccrs
from pylot.tomography.fmtomo_tools.compare_arrivals_hybrid import organize_receivers, organize_sources, organize_event_names
from pylot.tomography.map_utils import make_map
from pylot.tomography.utils import normed_figure
def plot_map(otimes_fname, sources_fname, receivers_fname, isf, rtimes_fname=None, arrivals_fname=None, colorbar=True,
title=True, stack=False, source_ids=(), savefig_dir='', demean=True, absolute=False, clat=42.25, clon=17.5,#clat=46., clon=11.,
file_ext='png', max_abs=3., relative_to_otimes=False, only_otimes=False):
if savefig_dir:
if not os.path.isdir(savefig_dir):
os.mkdir(savefig_dir)
src_dict = organize_sources(sources_fname)
rec_dict = organize_receivers(receivers_fname)
eventnames = organize_event_names(isf)
residuals_dict = read_fmtomo_tt_file(otimes_fname, rec_dict, rtimes_fname=rtimes_fname, synth_fname=arrivals_fname,
demean=demean, absolute=absolute, height_correction_vp=5.5,
relative_to_otimes=relative_to_otimes, only_otimes=only_otimes)
if stack:
residuals_dict = stack_sources(residuals_dict)
with open('stacked_residuals.json', 'w') as outfile:
json.dump(residuals_dict, outfile)
count = 0
if savefig_dir:
fig = normed_figure(width_cm=12, ratio=12./9.)
#fig = plt.figure(figsize=(12, 9))
else:
fig = plt.figure()
if stack or source_ids is not [] or savefig_dir:
count = 1
# increase point size if not stacked
sizefactor_stacked = {True: 30,
False: .2e4}
for src_id, dic in residuals_dict.items():
eventname = eventnames[src_id - 1]
if source_ids != []:
if not src_id in source_ids:
continue
if max_abs == 'auto':
max_abs = np.max(np.abs(np.array(dic['ttimes'])))
# if not stack and not savefig_dir and source_ids == []:
# count += 1
# if count == 1:
# ax = fig.add_subplot(3, 3, count)
# ax0 = ax
# else:
# ax = fig.add_subplot(3, 3, count, sharex=ax0, sharey=ax0)
#
# ax.text(0.1, 0.9, 'Source ID {}, {}'.format(src_id, src_dict[src_id]['phase']), transform=ax.transAxes)
# else:
# ax = fig.add_subplot(111)
if stack and title:
plt.title('Stacked {} sources. Size relates to number of stacks per receiver.'.format(len(src_dict)),
y=1.05)
ax = make_map(draw_model=False, draw_faults=True, model_legends=False, clon=clon, clat=clat,
width = 30, height = 21,) # , width=6e6, height=5e6)
if not stack:
slat, slon = src_dict[src_id]['lat'], src_dict[src_id]['lon']
dist = plot_source(slat, slon)
baz = gps2dist_azimuth(clat, clon, slat, slon, a=6.371e6, f=0)[1]
if title:
plt.title(
'Plot of source {}. Distance: {:.0f}$^\circ$. BAZ: {:.0f}$^\circ$'.format(eventname, dist, baz),
y=1.05)
#ax.set_xlabel('Longitude [$^\circ$]')
#ax.set_ylabel('Latitude [$^\circ$]')
# prepare grids for contour plot
#lonaxis = np.linspace(min(dic['lons']), max(dic['lons']), 250)
#lataxis = np.linspace(min(dic['lats']), max(dic['lats']), 250)
#longrid, latgrid = np.meshgrid(lonaxis, lataxis)
#ttimes_grid = griddata((dic['lats'], dic['lons']), dic['ttimes'], (latgrid, longrid), method='linear')
#levels = np.linspace(min(dic['ttimes']), max(dic['ttimes']), 75)
cmap = plt.get_cmap('seismic') if not absolute else plt.get_cmap('viridis')
vmin = -max_abs if not absolute else None
vmax = max_abs if not absolute else None
sc = ax.scatter(dic['lons'], dic['lats'], c=np.array(dic['ttimes']), cmap=cmap,
vmin=vmin, vmax=vmax, edgecolors='grey', linewidths=0.2,
s=sizefactor_stacked[stack]*np.array(dic['nttimes'])/len(src_dict), zorder=10,
transform=ccrs.PlateCarree(), alpha=0.75)
sc.set_edgecolor('0.')
if colorbar:
cb = plt.colorbar(sc, label='ttime [s]', shrink=0.5)
if stack:
savefig_path = os.path.join(savefig_dir, 'stacked_events.{}'.format(file_ext))
else:
#fname = f'baz{baz:03.0f}_dist{dist:03.0f}_{eventname}_srcID{src_id}.{file_ext}'
fname = f'{eventname}_srcID{src_id}.{file_ext}'
savefig_path = os.path.join(savefig_dir, fname)
if not savefig_dir and not count % 9:
plt.show()
fig = plt.figure()#figsize=(16, 9), dpi=300.)
count = 0
if savefig_dir:
ax.figure.savefig(savefig_path, dpi=300., bbox_inches='tight', pad_inches=0.)
print('Wrote file {}'.format(savefig_path))
plt.clf()
if not savefig_dir:
plt.show()
def plot_source(lat, lon, clat=46., clon=11.):
#basemap.drawgreatcircle(lon, lat, clon, clat, color='k', zorder=15, linestyle='dashed')
dist, azim, bazim = gps2dist_azimuth(lat, lon, clat, clon, a=6.371e6, f=0)
dist_deg = dist/1000./np.pi/2./6371.*360.
return dist_deg
#x = np.cos(np.deg2rad(azim))
#y = np.sin(np.deg2rad(azim))
#print(x, y)
#ax.plot([0, x], [0, y], 'r', zorder=15)
def read_fmtomo_tt_file(otimes_fname, rec_dict, synth_fname=None, rtimes_fname=None, demean=True, absolute=False,
height_correction_vp=None, R=6371., relative_to_otimes=True, only_otimes=False):
#with open(otimes_fname, 'r') as infile:
# # skip first line for otimes.dat. Frist line contains N_rays
# obsarray = infile.readlines()[1:]
obsarray = np.loadtxt(otimes_fname, skiprows=1)
if height_correction_vp:
if absolute:
print('APPLICATION OF HEIGHT CORRECTION FOR ABS NOT IMPLEMENTED YET. Not needed!?')
#print('Applying height correction of {} km/s'.format(height_correction_vp))
else:
height_correction_vp = None
print('Will not apply height correction for relative values.')
if synth_fname:
synth_array = np.genfromtxt(synth_fname)
if rtimes_fname:
ref_array = np.genfromtxt(rtimes_fname)
if synth_fname and rtimes_fname:
#with open(synth_fname, 'r') as infile:
# synth_array = infile.readlines()
#with open(rtimes_fname, 'r') as infile:
# ref_array = infile.readlines()
#mean_obs = np.mean(obsarray[:, 4])
#mean_synth = np.mean(synth_array[:, 4])
#mean_ref = np.mean(ref_array[:, 4])
#mean_rel_synth = mean_synth - mean_ref
#print('Means:\nobserved: {}, relative {}'.format(mean_obs, mean_rel_synth))
synth_src_means = {}
nsrc = obsarray[-1, 1]
for index in range(int(nsrc)):
srcid = index + 1
indices = np.where(synth_array[:, 1].astype(int) == srcid)
synth_src_means[srcid] = np.mean(synth_array[indices, 4] - ref_array[indices, 4])
print('Average mean for sources: {} s'.format(np.mean(list(synth_src_means.values()))))
if demean:
print('Removing mean from synthetic times...')
else:
print('Will not mean-correct travel times')
# mean_rel_synth = 0.
residuals_dict = {}
for index, line in enumerate(obsarray):
rec_id = int(line[0])
src_id = int(line[1])
ttime = line[4] if relative_to_otimes or only_otimes else 0
# get residuals from reference file if fname is given
if synth_fname:
# get synthetic time
ttime_syn = synth_array[index][4]
if rtimes_fname:
# get reference time (1d travel time)
ttime_ref = ref_array[index][4]
if synth_fname and rtimes_fname:
# calculate synthetic time relative to 1d travel time
ttime_rel_syn = ttime_syn - ttime_ref
#print(ttime_ref, ttime_syn, ttime_rel_syn, ttime, ttime-ttime_rel_syn)
# calculate difference between relative observed and model times, multiply with -1 to get tsynth - tobs
mean_rel_src = synth_src_means[srcid] if demean else 0.
if not only_otimes:
ttime = -1 * (ttime_rel_syn - ttime - mean_rel_src)
if absolute:
ttime = ttime_syn
try:
uncertainty = line[5]
except:
uncertainty = 0
# create dictionary for source if not exists
if not src_id in residuals_dict.keys():
residuals_dict[src_id] = {'lats': [],
'lons': [],
'rads': [],
'ttimes':[],
'nttimes': [],
'uncerts': []}
# chose correct dictionary
dic = residuals_dict[src_id]
# get coordinates from organized receivers dictionary
dic['lats'].append(rec_dict[rec_id]['lat'])
dic['lons'].append(rec_dict[rec_id]['lon'])
dic['rads'].append(rec_dict[rec_id]['rad'])
# rad is depth?
#elev = - rec_dict[rec_id]['rad']
#height_correction_vp = 0
#station_height_corr = elev / (height_correction_vp) if height_correction_vp else 0.
#if elev < 0: print(elev, station_height_corr)
# MP MP NO HEIGHT CORRECTION FOR FMTOMO! REFTIMES CONTAIN STATION HIGHT!!!!!
dic['ttimes'].append(ttime) # - station_height_corr)
# number of ttimes will be set to 1 (determines size in scatter, only relevant for stacked ttimes)
dic['nttimes'].append(1)
dic['uncerts'].append(uncertainty)
return residuals_dict
def stack_sources(residuals_dict):
all_ttimes = {}
all_uncs = {}
for src_id, dic in residuals_dict.items():
for index in range(len(dic['lats'])):
lat = dic['lats'][index]
lon = dic['lons'][index]
rad = dic['rads'][index]
source_tuple = (lat, lon, rad)
if not source_tuple in all_ttimes.keys():
all_ttimes[source_tuple] = []
all_uncs[source_tuple] = []
all_ttimes[source_tuple].append(dic['ttimes'][index])
all_uncs[source_tuple].append(dic['uncerts'][index])
# create new dictionary in the shape of residuals dict with only one source
stacked_dict = {}
stacked_dict[1] = {'lats': [],
'lons': [],
'rads': [],
'ttimes':[],
'nttimes': [],
'misfits': []}
for source_tuple in all_ttimes.keys():
ttimes_list = all_ttimes[source_tuple]
uncs_list = all_uncs[source_tuple]
misfit = np.sum((np.array(ttimes_list) / np.array(uncs_list))**2) / len(ttimes_list)
lat, lon, rad = source_tuple
stacked_dict[1]['lats'].append(lat)
stacked_dict[1]['lons'].append(lon)
stacked_dict[1]['rads'].append(rad)
stacked_dict[1]['ttimes'].append(np.sum(np.mean(ttimes_list)))
stacked_dict[1]['nttimes'].append(len(ttimes_list))
stacked_dict[1]['misfits'].append(misfit)
#stacked_dict[1]['ttimes_list'].append(ttimes_list)
ttimes = [d['ttimes'] for d in stacked_dict.values()]
plt.hist(ttimes, 100)
plt.axvline(np.mean(ttimes), c='r')
plt.xlabel('ttime [s]')
return stacked_dict
def compare_residuals():
wdir = '/rscratch/minos13/marcel/fmtomo_alparray/'
os.chdir(wdir)
with open('alparray_mantle_diehl_crust_included_v3_hf/stacked_residuals.json', 'r') as infile:
sr = json.load(infile)
with open('alparray_mantle_diehl_crust_included_v3_no_density_hf/stacked_residuals.json', 'r') as infile:
srnd = json.load(infile)
mfdiff = np.array(sr['1']['misfits']) - np.array(srnd['1']['misfits'])
np.mean(mfdiff)
sc = plt.scatter(sr['1']['lons'], sr['1']['lats'], c=mfdiff, cmap='seismic', vmin=-7.5, vmax=7.5)
cb = plt.colorbar(sc)
title = 'Difference in station misfit after 12 iterations (density kernel - no density).' \
' Blue: decrease in residuals using density. Mean: {:.4f}'
plt.title(title.format(np.mean(mfdiff)))
plt.xlabel('Latitude (deg)')
plt.ylabel('Longitude (deg)')
cb.set_label('Misfit')
sc.set_linewidth(0.2)
sc.set_edgecolor('k')
plt.show()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Plot residuals of fm3d output file (arrivals.dat format) on map.')
parser.add_argument('--otimes', default='otimes.dat', help='otimes.dat file (default: otimes.dat)')
parser.add_argument('--sources', default='sources.in', help='sources.in file (default: sources.in)')
parser.add_argument('--receivers', default='receivers.in', help='receivers.in file (default receivers.in)')
parser.add_argument('--sourcefile', default='input_source_file.in', help='input_source_file (default: input_source_file.in)')
parser.add_argument('--arrivals', default=None, help='arrivals_file')
parser.add_argument('--rtimes', default=None, help='reference_file')
parser.add_argument('--fext', default='png', help='file extension for images (if -w)')
parser.add_argument('-s', '--stack', action='store_true', dest='stack', help='stack picks')
parser.add_argument('-w', '--write', action='store_true', default=False, help='write figures to disk')
parser.add_argument('-a', '--abs', action='store_true', dest='abs', default=False, help='compute absolute values')
parser.add_argument('-nd', '--no_demean', action='store_true', default=False, help='do not demean travel times')
parser.add_argument('-nc', '--no_colorbar', action='store_true', default=False, help='do not plot colorbar')
parser.add_argument('-nt', '--no_title', action='store_true', default=False, help='do not plot title')
parser.add_argument('-no', '--no_otimes', action='store_true', default=False, help='do not plot relative to otimes')
parser.add_argument('-oo', '--only_otimes', action='store_true', default=False, help='only plot observed times (otimes)')
parser.add_argument('-i', '--ids', dest='source_ids', type=int, default=[], nargs='*',
help='Plot only one source with FMTOMO internal id(s).')
args = parser.parse_args()
savefig_dir = 'residual_maps_out' if args.write else ''
plot_map(args.otimes, args.sources, args.receivers, args.sourcefile, arrivals_fname=args.arrivals,
rtimes_fname=args.rtimes, stack=args.stack, source_ids=args.source_ids, colorbar=not args.no_colorbar,
title=not args.no_title, savefig_dir=savefig_dir, demean=not args.no_demean, absolute=args.abs,
file_ext=args.fext, relative_to_otimes=not args.no_otimes, only_otimes= args.only_otimes)
@@ -0,0 +1,270 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Estimate ray crossing at each depth using rays (as npy objects) precalculated from FMTOMO file rays.dat
(e.g. script: plot_rays_on_plane)
'''
import os
import glob
import json
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import RegularGridInterpolator
from pylot.tomography.fmtomo_tools.fmtomo_grid_utils import read_vgrid_regular, read_vgrid
from pylot.tomography.map_utils import angle_marker, make_map
pjoin = os.path.join
def main(fdir, it=1, station_coords_file='/data/AdriaArray_Data/various/station_coords.json'):
fnin_vgrid = pjoin(fdir, f'it_{it}/vgrids.in')
fnout_vtk = pjoin(fdir, f'vgrids_it{it}_w_ray_crossings.vtk')
fnout_npy = pjoin(fdir, f'vgrids_hf_it{it}' + '_crossing_bins_{}-{}_mincount_{}_STEP_{}.npy')
# binsize (degrees), min_count for each direction to raise quality by 1 increment
binsize_az = 90.
binsize_incl = 60.
min_count = 5
plot = False
write = True
draw_hists = True
draw_simple_hists = True
step = 2 # take nth sample of vgrid for calculating cells
# for stations plotting
with open(station_coords_file, 'r') as infile:
stations = json.load(infile)
lonsStations, latsStations = zip(*[(sta['longitude'], sta['latitude']) for sta in stations.values()])
# some constants etc.
R = 6371.
bins_azim = np.arange(0, 360 + binsize_az, binsize_az)
bins_incl = np.arange(0., 60. + binsize_incl, binsize_incl)
nbins_azim = len(bins_azim) - 1
nbins_incl = len(bins_incl) - 1
# for plotting:
bins = bins_azim
binsize = binsize_az
nbins = nbins_azim
# get vgrid and estimate dlat/dlon
vgrid_reg = read_vgrid_regular(fnin_vgrid)
lons, lats, rads = [array[::step] for array in vgrid_reg[0]]
vps, covs, sms, pdvs = [array[::step, ::step, ::step] for array in vgrid_reg[1:]]
dlat = lats[1] - lats[0]
dlon = lons[1] - lons[0]
# grid = init_dict()
# grid['vps'] = list(vps.ravel())
# grid['covs'] = list(covs.ravel())
# grid['sms'] = list(sms.ravel())
# grid['pdvs'] = list(pdvs.ravel())
# grid['res'] = []
#
# for rad in rads:
# for lat in lats:
# for lon in lons:
# depth = R - rad
# grid['lons'].append(lon)
# grid['lats'].append(lat)
# grid['depths'].append(depth)
# x, y, z = pol2cart(lat, lon, R - depth)
# grid['xs'].append(x)
# grid['ys'].append(y)
# grid['zs'].append(z)
# just for plotting at certain depths (not for write!!!)
depths = np.arange(0., 800., 100)
if not write:
rads = R - depths
resolutions = []
lons_export = []
lats_export = []
rads_export = []
print(rads)
for rad in rads:
# prepare dict containing horizontal angle for key tuple (ilat, ilon)
vgrid_azim_ids = {}
vgrid_incl_ids = {}
print('Working on radius', rad)
# iterate over rays
for fnin_rays in glob.glob1(fpath_events, '*.npz'):
rays = np.load(os.path.join(fpath_events, fnin_rays), allow_pickle=True)
# raypoints = np.zeros((len(rays), 3))
#for index in range(len(rays)):
for ray in rays.values():
#ray = rays[index]
# get index of closest depth here from ray (TAKE CARE OF ALIASING! Using nearest value)
ind_min = np.abs(ray[:, 0] - rad).argmin()
# in case ind_min is at upper boundary (surface)
if ind_min == len(ray[:, 0]) - 1:
ind_min -= 1
# get diffs to following ray index (km, deg, deg)!
ray_diff = np.diff(ray[ind_min:ind_min + 2], axis=0)[0]
lat = ray[ind_min, 1]
lat_diff_km = ray_diff[1] * (np.pi * R) / 180.
lon_diff_km = ray_diff[2] * (np.pi * np.cos(np.deg2rad(lat)) * R) / 180.
r_diff_km = ray_diff[0]
dlateral = np.sqrt(lat_diff_km ** 2 + lon_diff_km ** 2)
# calculate horizontal angle
azim = np.rad2deg(np.arctan2(lon_diff_km, lat_diff_km))
incl = np.rad2deg(np.arctan(dlateral / r_diff_km))
# correct angle from -180-180 to 0-360 and also change azim to bazim
bazim = azim + 180.
# angles[index] = angle
# raypoints[index] = ray[ind_min]
lat, lon = ray[ind_min, 1:]
lati = np.where((lats <= lat + dlat / 2.) & (lats > lat - dlat / 2.))[0][0]
loni = np.where((lons <= lon + dlon / 2.) & (lons > lon - dlon / 2.))[0][0]
key = (lati, loni)
if not key in vgrid_azim_ids.keys():
vgrid_azim_ids[key] = []
vgrid_incl_ids[key] = []
vgrid_azim_ids[key].append(bazim)
vgrid_incl_ids[key].append(incl)
# vgrid_ids[index] = np.array([lati, loni])
# plt.scatter(lons[loni], lats[lati], c='r', marker='o', facecolor='none', alpha=0.5)
# sc = plt.scatter(raypoints[:,2], raypoints[:,1], c=angles[:])
vgrid_angles_quality = {}
vgrid_azim_hist = {}
for inds in vgrid_azim_ids.keys():
azims = vgrid_azim_ids[inds]
incls = vgrid_incl_ids[inds]
lati, loni = inds
hist_az, _ = np.histogram(azims, bins=bins_azim)
hist_incl, _ = np.histogram(incls, bins=bins_incl)
hist2d, _, _ = np.histogram2d(azims, incls, bins=[bins_azim, bins_incl])
# hist_az, hist_inc = hist2d
hist = hist2d.ravel()
quality = len(hist[hist > min_count - 1]) / (nbins_azim * nbins_incl)
# quality = len(hist_az[hist_az > min_count - 1]) / nbins_azim
# quality = len(hist_incl[hist_incl > min_count - 1]) / nbins_incl
vgrid_angles_quality[inds] = quality
vgrid_azim_hist[inds] = hist_az
# LATS = []
# LONS = []
qualities = []
hists = []
for ilat, lat in enumerate(lats):
for ilon, lon in enumerate(lons):
# LATS.append(lat)
# LONS.append(lon)
key = (ilat, ilon)
quality = vgrid_angles_quality.get(key)
hist = vgrid_azim_hist.get(key)
# print(key, quality)
if not quality:
quality = 0.
# hist for plotting only!
if hist is None:
hist = np.zeros(nbins)
qualities.append(quality)
hists.append(hist)
lons_export.append(lon)
lats_export.append(lat)
rads_export.append(rad)
resolutions += qualities
if plot:
# TODO: still old basemap code
raise NotImplementedError('Still using old basemap code')
fig = plt.figure()
ax = fig.add_subplot(111)
bmap = make_map(ax, resolution='l')
LONS, LATS = np.meshgrid(lons, lats)
sc = bmap.pcolormesh(LONS - dlon / 2., LATS - dlat / 2.,
np.array(qualities).reshape(LATS.shape), latlon=True, zorder=1.5,
shading='nearest') # , s=np.array(qualities)*100)
# sc = plt.contourf(LONS, LATS, np.array(qualities).reshape(LATS.shape), levels=21)
if draw_hists:
for index, bin in enumerate(bins[:-1]):
marker = angle_marker(bin, bin + binsize)
s = np.array(hists)[:, index]
if draw_simple_hists:
s[s < min_count] = 0
s[s >= min_count] = 150
else:
s *= 10.
sc_angle = bmap.scatter(LONS, LATS, s=s, marker=marker, edgecolors='0.75', alpha=1., linewidths=0.6,
latlon=True, zorder=1.5, )
sc_angle.set_facecolor('none')
# sc_angle = plt.scatter(LONS, LATS, s=1, marker='.', edgecolors='1.', alpha=1., linewidths=1.)
bmap.scatter(lonsStations, latsStations, c='k', s=0.5, latlon=True, zorder=1.5, ) # , alpha=0.5)
plt.title('Azimuthal coverage at depth of {}km, {} bins'.format((R - rad), nbins_azim * nbins_incl))
# plt.xlim([0, 22])
# plt.ylim([40, 53])
# plt.gca().set_aspect('equal')
cbar = plt.colorbar(sc)
cbar.set_label('Azimuthal coverage')
plt.show()
if write:
rginter_res = RegularGridInterpolator((rads, lats, lons),
np.array(resolutions).reshape((len(rads), len(lats), len(lons))),
bounds_error=False, fill_value=0.)
grid = read_vgrid(fnin_vgrid)[0]
grid['res'] = []
for lon, lat, depth in zip(grid['lons'], grid['lats'], grid['depths']):
grid['res'].append(rginter_res((R - depth, lat, lon)))
a = np.array([lons_export, lats_export, rads_export, resolutions])
np.save(fnout_npy.format(nbins_azim, nbins_incl, min_count, step), a)
write_vtk(grid, fnout_vtk,
write_data=['vps', 'res', 'covs', 'frechs']) # , clon=11., clat=46., dlon=12., dlat=6., sort=True)
def rays_to_npy(infile, fnout_npy, n_points=10):
rays = {}
i = 0
src_id_old = 1
while True:
i += 1
l1 = infile.readline()
if l1 == '': break
rec_id, src_id, ray_id = [int(item) for item in l1.split()[:3]]
if not src_id in rays.keys():
rays[src_id] = []
l2 = infile.readline()
n = int(l2.split()[0])
ray = np.zeros((n, 3))
for index in range(n):
r, lat, lon = [float(item) for item in infile.readline().split()]
ray[index] = np.array([r, np.rad2deg(lat), np.rad2deg(lon)])
rays[src_id].append(ray[::n_points])
dirname = os.path.split(fnout_npy)[0]
if not os.path.isdir(dirname):
os.mkdir(dirname)
for src_id, ray in rays.items():
np.savez(fnout_npy.format(src_id), *ray)
if __name__ == '__main__':
fmtomodir = '/data/AdriaArray_Data/fmtomo_adriaarray/alpadege/crust_incl_TESAURO_sm30_dm1/'
it = 12
fpath_events = pjoin(fmtomodir, f'rays_npy_it{it}/')
infile = open(pjoin(fmtomodir, f'it_{it}/rays.dat'), 'r')
fnout_npy = pjoin(fpath_events, f'it_{it}_rays_event_' + '{}.npz')
rays_to_npy(infile, fnout_npy)
main(fmtomodir, it=it)
@@ -0,0 +1,43 @@
import os
import numpy as np
import matplotlib.pyplot as plt
fpaths = ['/data/AlpArray_Data/fmtomo/v6/fwi_model_wolfgang_test',
'/data/AlpArray_Data/fmtomo/v6/final']
arr_path_ref = 'rtimes_tele.dat'
iterations = [0, 1, 2, 3, 4]
labels = ['rel_1d'] + [f'rel_it{it}' for it in iterations]
colors = plt.get_cmap('viridis')(np.linspace(0,1, len(iterations)))
colors = np.vstack(((.5, .5, .5, 1), colors))
axes = []
for fpath in fpaths:
rtimes = np.genfromtxt(os.path.join(fpath, arr_path_ref))[:, 4]
otimes = np.genfromtxt(os.path.join(fpath, 'otimes.dat'), skip_header=1)[:, 4]
res = np.empty((len(rtimes), len(iterations) + 1))
res[:, 0] = otimes # = otimes + rtimes - rtimes
for index, itr in enumerate(iterations):
arr_path = f'it_{itr}/arrivals.dat'
arrivals = np.genfromtxt(os.path.join(fpath, arr_path))[:, 4]
res[:, index + 1] = (otimes + rtimes) - arrivals # otimes (rel) + rtimes => otimes (abs)
fig = plt.figure()
ax = fig.add_subplot(111)
axes.append(ax)
ax.hist(res, bins=np.linspace(-5, 5, 50), rwidth=0.9, color=colors, label=labels)
ax.set_xlim(-1.5, 1.5)
plt.title(fpath)
ax.legend()
ylim_max = max([ax.get_ylim()[1] for ax in axes])
for ax in axes:
ax.set_ylim(0, ylim_max)
plt.show()
@@ -0,0 +1,187 @@
# This script estimates density of station distribution using Gaussian KDE. For each point a Gaussian Kernel with
# sigma ~ grid size (?) is summed in 2D, calculating estimate density function. If the sum of all points evaluated
# at the data points increases that means there are more regions with high density
import os
import copy
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import gaussian_kde
from pylot.tomography.fmtomo_tools.fmtomo_teleseismic_utils import organize_receivers, organize_event_names, export_otimes
def get_events_dict(otimes, recs):
events_dict = {}
for otime in otimes:
rec_id, src_id = int(otime[0]), int(otime[1])
if not src_id in events_dict.keys():
events_dict[src_id] = dict(lons=[], lats=[], rec_ids=[])
events_dict[src_id]['lons'].append(recs[rec_id]['lon'])
events_dict[src_id]['lats'].append(recs[rec_id]['lat'])
events_dict[src_id]['rec_ids'].append(rec_id)
return events_dict
def transfer_kde_to_weight(kdes):
weights = 0.1 - kdes
indices_negative = np.where(weights < 0)[0]
if indices_negative:
print('Warning! {} negative indices in weights. Set to 0.'.format(len(indices_negative)))
weights[indices_negative] = 0.
return weights
def calc_event_kernels(events_dict, eventnames, kde_factor, plot_single=False, fdir_out=None):
kernels = {}
evaluated_kernels = {}
if plot_single:
fig = plt.figure(figsize=(16, 9))
for index in range(len(eventnames)):
eventname = eventnames[index]
source = index + 1
lons = events_dict[source]['lons']
lats = events_dict[source]['lats']
kernel = gaussian_kde(np.array([lons, lats]), bw_method=kde_factor)
evaluated_kernel = kernel((lons, lats))
kernels[source] = kernel
evaluated_kernels[source] = evaluated_kernel
if plot_single:
assert fdir_out, 'Need to specify output directory for plots'
if not os.path.isdir(fdir_out):
os.mkdir(fdir_out)
weights = transfer_kde_to_weight(evaluated_kernel)
iterdict = {'kde': evaluated_kernel, 'weight': weights}
for name, colorarray in iterdict.items():
if name == 'kde':
vmin=0
vmax=0.05
else:
vmin=None
vmax=None
scs = plt.scatter(lons, lats, marker='o', c=colorarray, lw=1.0, vmin=vmin, vmax=vmax)
cbar = plt.colorbar(scs)
plt.title('Station {} for event {}'.format(name, eventname))
fig.savefig(os.path.join(fdir_out, '{}_{}.svg'.format(eventname, name)))
fig.clear()
#plt.show()
if plot_single:
plt.close('all')
return kernels, evaluated_kernels
def plot_hist_kernels(evaluated_kernels):
all_kernels = np.array([])
for kernel in evaluated_kernels.values():
all_kernels = np.append(all_kernels, kernel)
plt.hist(all_kernels, bins=200, label='kernels')
plt.title('Distribution of all kernels.')
plt.show()
def plot_hist_uncertainties(otimes_new, otimes_orig, bins=200):
plt.hist(otimes_orig[:, -1], lw=1, bins=bins, label='Unmodified uncertainties', fc=(.5, .5, 0., 0.5))
plt.hist(otimes_new[:, -1], lw=1, bins=bins, label='New uncertainties', fc=(0., .5, .5, 0.5))
plt.legend()
plt.xlabel('Uncertainty [s]')
plt.title('Uncertainty distribution before and after correction.')
plt.show()
def plot_uncertainties(otimes, recs, eventnames, fdir_out, vmin=0.1, vmax=0.4):
if not os.path.isdir(fdir_out):
os.mkdir(fdir_out)
print('Writing output to directory:', fdir_out)
epsilon = 1e-6
fig = plt.figure(figsize=(16, 9))
for index, eventname in enumerate(eventnames):
src_id = index + 1
indices = np.where(abs(otimes[:, 1] - src_id) <= epsilon)
rec_ids = otimes[:, 0][indices]
uncs = otimes[:, -1][indices]
lats = [recs[rec_id]['lat'] for rec_id in rec_ids]
lons = [recs[rec_id]['lon'] for rec_id in rec_ids]
sc = plt.scatter(lons, lats, c=uncs, vmin=vmin, vmax=vmax)
plt.title(eventname)
cb = plt.colorbar(sc)
fig.savefig(os.path.join(fdir_out, '{}.svg'.format(eventname)))
fig.clear()
plt.close('all')
def exp_func(uncertainty, eval_kernel, exponent=30):
return uncertainty * np.exp(exponent * eval_kernel)
def modify_otimes(otimes, recs, kernels):
print('Applying Kernel on otimes and modifying uncertainties...')
for otime in otimes:
rec_id, src_id = int(otime[0]), int(otime[1])
lon = recs[rec_id]['lon']
lat = recs[rec_id]['lat']
eval_kernel = kernels[src_id]((lon, lat))
otime[-1] = exp_func(otime[-1], eval_kernel)
return otimes
def plot_average_kernel(evaluated_kernels, eventnames):
ids_names = [(index + 1, eventname) for index, eventname in enumerate(eventnames)]
sorted_events = sorted(ids_names, key=lambda x: x[1])
kernel_sums = {src_id: np.sum(kernel) for src_id, kernel in evaluated_kernels.items()}
src_ids = [item[0] for item in sorted_events]
eventnames = [item[1] for item in sorted_events]
sums = [kernel_sums[src_id] for src_id in src_ids]
plt.plot(sums)
xticks = np.arange(0, len(eventnames), step=len(eventnames)//10)
xticklabels = [eventname[:8] for index, eventname in enumerate(eventnames) if index in xticks]
plt.xticks(xticks, xticklabels)
plt.title('Average kernel per station. High value means inhomogeneous station distribution.')
plt.show()
def export_station_kdes(events_dict, evaluated_kernels, fnout):
with open(fnout, 'w') as outfile:
for src_id in range(1, len(events_dict) + 1):
kernel = evaluated_kernels[src_id]
for kde, rec_id in zip(kernel, events_dict[src_id]['rec_ids']):
outfile.write('{} {} {}\n'.format(rec_id, src_id, kde))
def main(plot=False, plot_detailed=False):
working_path = '/rscratch/minos13/marcel/fmtomo_alparray/v4/alparray_mantle_diehl_crust_included_hf_gradient_smoothing'
fdir_out = 'station_density'
fnout = os.path.join(fdir_out, 'station_kdes.txt')
os.chdir(working_path)
eventnames = organize_event_names('input_source_file_P.in')
recs = organize_receivers('receivers.in')
otimes_data = np.genfromtxt('otimes_orig.dat', skip_header=1)
# kernel width ~ grid size? or station spacing?
kde_size = 30 # km
kde_factor = kde_size / (6371. * np.pi / 180.)
print('KDE width (degree):', kde_factor)
events_dict = get_events_dict(otimes_data, recs)
kernels, evaluated_kernels = calc_event_kernels(events_dict, eventnames, kde_factor, plot_single=plot_detailed,
fdir_out=fdir_out)
export_station_kdes(events_dict, evaluated_kernels, fnout)
otimes_modif = modify_otimes(copy.deepcopy(otimes_data), recs, kernels)
#export_otimes(otimes_modif, os.path.join(working_path, 'otimes_modif_kernel.dat'))
if plot:
plot_average_kernel(evaluated_kernels, eventnames)
plot_hist_kernels(evaluated_kernels)
plot_hist_uncertainties(otimes_modif, otimes_data)
if plot_detailed:
plot_uncertainties(otimes_data, recs, eventnames, 'uncerts_old')
plot_uncertainties(otimes_modif, recs, eventnames, 'uncerts_new')
if __name__ == '__main__':
main(plot=True, plot_detailed=True)
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
ulimit -s 8192
conda activate pylot_311
##qsub -l low -cwd -l "os=*stretch" -pe mpi-fu 40 submit_fmtomo.sh
#$ -l low
#$ -cwd
#$ -pe smp 40
#$ -q "*@minos11, *@minos12, *@minos15"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot_tools/"
python fmtomo.py
@@ -0,0 +1,10 @@
#!/bin/bash
conda activate py37
#qsub -l low -cwd -l "os=*stretch" -pe mpi-fu 40 submit_fmtomo_grid_utils.sh
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot_tools/"
python fmtomo_grid_utils.py
@@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from pylot.tomography.fmtomo_utils import Tomo3d
#tomo = Tomo3d('/rscratch/minos13/marcel/fmtomo_alparray/alparray_mantle_from_m6.0_tesauro_model_on_top',
# '/rscratch/minos13/marcel/fmtomo_alparray/alparray_mantle_from_m6.0_tesauro_model_on_top',
# buildObs=False, saveRays=False)
wdir = '/rscratch/minos13/marcel/fmtomo_alparray/'
#directories = ['alparray_mantle_from_m6.0_diehl_crustal_corrections_sm1_damp3',
#'alparray_mantle_from_m6.0_diehl_crustal_corrections_sm1_damp30',]
directories= ['alparray_mantle_from_m6.0_diehl_crustal_corrections_sm10_damp3',
'alparray_mantle_from_m6.0_diehl_crustal_corrections_sm10_damp30']
for dire in directories:
path = os.path.join(wdir, dire)
tomo = Tomo3d(path, path, buildObs=False, saveRays=False)
tomo.runTOMO3D(40, 8)
@@ -0,0 +1,12 @@
#!/bin/bash
ulimit -s 8192
#$ -l low,os=*stretch
#$ -l h_vmem=5G
#$ -cwd
#$ -pe smp 40
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot_tools/"
python submit_fmtomo_run.py
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
ulimit -s 8192
conda activate pylot_311
##qsub -l low -cwd -l "os=*stretch" -pe mpi-fu 40 submit_fmtomo_teleseismic.sh
#$ -l low
##$ -l os=*stretch
#$ -cwd
#$ -pe smp 40
#$ -q "*@minos11, *@minos12, *@minos15"
### WARNING fm3d_prepare_tele not compiled correctly for all cluster machines (e.g. not working on minos12, minos15)
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot/"
export PYTHONPATH="$PYTHONPATH:/home/marcel/git/pylot_tools/"
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_mantle_0.03-0.1_revised' \
#'/data/AlpArray_Data/fmtomo/v6/crust_incl_lf_sm_FIX_DTS_grad_sm30_dm10' \
#'_correlated_0.03-0.1' -n $NSLOTS --blacklist '/rscratch/minos22/marcel/alparray/station_blacklist.csv'
# --model 'ak135_diehl_v2'
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_mantle_0.03-0.5_revised' \
#'/data/AlpArray_Data/fmtomo/v6/crust_incl_hf_sm_FIX_DTS_grad_sm30_dm10' \
#'_correlated_0.03-0.5' -n $NSLOTS --blacklist '/rscratch/minos22/marcel/alparray/station_blacklist.csv'
# --model 'ak135_diehl_v2'
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_mantle_0.01-0.2_S_SKS' \
#'/data/AlpArray_Data/fmtomo/v6_S/crust_incl_hf_KSTL_grad_sm6_dm10/' \
#'_correlated_0.01-0.2_S*' -n $NSLOTS --blacklist '/data/AlpArray_Data/various/alparray/station_blacklist.csv'
# --model 'ak135_diehl_v2'
#
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_mantle_0.03-0.5_revised' \
# '/rscratch/minos13/marcel/fmtomo_alparray/v3.5/alparray_mantle_diehl_crust_included_hf_no_init' \
# '_correlated_0.03-0.5' -n $NSLOTS --no_write_init_nodes --model 'ak135_diehl_v2'
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_hybrid_test' \
# '/rscratch/minos13/marcel/fmtomo_alparray/v3/hybrid_method_test_pgrid_fine' \
# '' -n $NSLOTS #--blacklist '/rscratch/minos22/marcel/alparray/station_blacklist.csv' --model 'ak135_diehl_v2'
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_hybrid_test_diehl_v2' \
#'/data/AlpArray_Data/fmtomo/v6/hybrid_method_test' \
#'' -n $NSLOTS --model 'ak135_diehl_v2'
#--blacklist '/rscratch/minos22/marcel/alparray/station_blacklist.csv'
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_hybrid_test_diehl_v2_orig_box_more_stations' \
#'/data/AlpArray_Data/fmtomo/v5/hybrid_method_tests_diss/v5_orig_box_size_more_receiver_no_demean_CORRECTED_depth_sampling' \
#'' -n $NSLOTS --model 'ak135_diehl_v2'
#python fmtomo_teleseismic.py '/data/AlpArray_Data/dmt_database_synth_model_mk6_it3_no_rotation' \
#'/data/AlpArray_Data/fmtomo/v6_resolution_analysis/fwi_mk6_it3_P' \
#'_correlated_P' -n $NSLOTS --model 'ak135_diehl_v2'
#python fmtomo_teleseismic.py '/data/AdriaArray_Data/dmt_database_alpadege' \
#'/data/AdriaArray_Data/fmtomo_adriaarray/alpadege/no_crust_correction' \
#'_correlated_0.03-0.5' -n $NSLOTS --model 'ak135'
python fmtomo_teleseismic.py '/data/AdriaArray_Data/dmt_database_minimal' \
'/data/AdriaArray_Data/fmtomo_adriaarray/alpadege/test_minimal' \
'_correlated_0.03-0.5' -n $NSLOTS --model 'ak135'
@@ -0,0 +1,266 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import glob
import subprocess
import json
import numpy as np
import numpy.polynomial.polynomial as poly
from scipy.sparse import spdiags
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from itertools import cycle
from pylot.tomography.fmtomo_tools.fmtomo_grid_utils import read_vgrid
# def calc_dampnorm(vgrid, vgrid_ref):
# # calculate m - m0
# m_m0 = np.array(vgrid['vps']) - np.array(vgrid_ref['vps'])
#
# # calculate inverse of diagonal elements of a priori model covariance matrix (which should be a diagonal matrix)
# # IMPORTANT: COVARIANCES ARE MOST LIKELY STANDARD DEVIATIONS -> square them
# covs = np.array(vgrid_ref['covs'])**2
#
# covs_inv = 1. / covs
#
# #cm_inv = spdiags(covs_inv, 0, covs_inv.size, covs_inv.size)#
#
# #norm_calc_old = np.dot(m_m0.transpose(), m_m0 * cm_inv)
#
# norm = np.dot(m_m0, m_m0 * covs_inv)
#
# return norm
#
#
# def calc_smoothnorm(vgrid, gridn, R=6371.):
# nR, nTheta, nPhi = gridn
#
# vps = np.array(vgrid['vps'])
# lats = np.array(vgrid['lats'])
# lons = np.array(vgrid['lons'])
# depths = np.array(vgrid['depths'])
# #vgridref = np.array(vgridref['vps'])
# #dvgrid = vgrid - vgridref
#
# vgarray = np.zeros((nR, nTheta, nPhi))
# lonsarray_km = np.zeros((nR, nTheta, nPhi))
# latsarray_km = np.zeros((nR, nTheta, nPhi))
# radsarray_km = np.zeros((nR, nTheta, nPhi))
# #vgarray_diff = np.zeros((nR, nTheta, nPhi))
# smootharray = np.zeros((nR, nTheta, nPhi))
# #for iLayer in range(nlayers):
# globInd = 0
# for iR in range(nR):
# for iTheta in range(nTheta):
# for iPhi in range(nPhi):
# r = R - depths[globInd]
# lat = lats[globInd]
# lon = lons[globInd]
# r_minor = np.cos(np.deg2rad(lat)) * r
# vgarray[iR, iTheta, iPhi] = vps[globInd]
# radsarray_km[iR, iTheta, iPhi] = r
# latsarray_km[iR, iTheta, iPhi] = np.pi * r * lat / 180.
# lonsarray_km[iR, iTheta, iPhi] = np.pi * r_minor * lon / 180.
# #vgarray_diff[iR, iTheta, iPhi] = vgrid[globInd]
# globInd += 1
#
# # iterate over grid diffs (correct?) and sum 1 * point left -2 * point + 1 * point right in all 3 dim.
# smsum = 0.
# for iR in range(nR):
# for iTheta in range(nTheta):
# for iPhi in range(nPhi):
# vg = vgarray[iR, iTheta, iPhi]
# sum1 = sum2 = sum3 = 0.
# if 0 < iPhi < nPhi - 1:
# h = abs(lonsarray_km[iR, iTheta, iPhi + 1] - lonsarray_km[iR, iTheta, iPhi - 1]) / 2
# sum1 = (vgarray[iR, iTheta, iPhi - 1] - 2 * vg + vgarray[iR, iTheta, iPhi + 1]) / h**2
# if 0 < iTheta < nTheta - 1:
# h = abs(latsarray_km[iR, iTheta + 1, iPhi] - latsarray_km[iR, iTheta - 1, iPhi]) / 2
# sum2 = (vgarray[iR, iTheta - 1, iPhi] - 2 * vg + vgarray[iR, iTheta + 1, iPhi]) / h**2
# if 0 < iR < nR - 1:
# h = abs(radsarray_km[iR - 1, iTheta, iPhi] - radsarray_km[iR + 1, iTheta, iPhi]) / 2
# sum3 = (vgarray[iR - 1, iTheta, iPhi] - 2 * vg + vgarray[iR + 1, iTheta, iPhi]) / h**2
# smsum += np.sqrt(sum1**2 + sum2**2 + sum3**2)
# #print(sum1, sum2, sum3, smsum)
# smootharray[iR, iTheta, iPhi] = smsum#sum1 + sum2 + sum3
#
# # m_T * D_T * D * m ?? todo: unsure
# norm = np.sum(smootharray ** 2)
#
# return norm, smootharray
from pylot.tomography.utils import normed_figure
def calc_smoothnorm(wdir, iter):
smv = np.loadtxt(os.path.join(wdir, 'it_{}/smv.out'.format(iter + 1)), skiprows=1)
dm = np.loadtxt(os.path.join(wdir, 'it_{}/dm.out'.format(iter + 1)), skiprows=1)
norm = np.sum(smv*dm)
return norm
def calc_dampnorm(wdir, iter):
ecmi = np.loadtxt(os.path.join(wdir, 'it_{}/ecmi.out'.format(iter + 1)), skiprows=1)
dm = np.loadtxt(os.path.join(wdir, 'it_{}/dm.out'.format(iter + 1)), skiprows=1)
norm = np.sum(ecmi * dm**2)
return norm
def calc_norm(wdir, iteration_number):
dampnorm = calc_dampnorm(wdir, iteration_number)
smoothnorm = calc_smoothnorm(wdir, iteration_number)
print('dampnorm: ', dampnorm)
print('smoothnorm: ', smoothnorm)
norm = dampnorm + smoothnorm
print('Calculated summed norm of', norm)
return norm, dampnorm, smoothnorm
def calc_tradeoff(fpath_in, fname_out=None, iteration_number = 12):
results = {}
for wdir in glob.glob(fpath_in):
#wdir = '/rscratch/minos13/marcel/fmtomo_alparray/alparray_mantle_from_m6.0_diehl_crustal_corrections_sm1000_damp100/'
smooth = float(wdir.split('_')[-2].split('sm')[-1])
damp = float(wdir.split('_damp')[-1].split('/')[0])
print('Calculating tradeoff for smoothing and damping of {}, {}'.format(smooth, damp))
if not smooth in results.keys():
results[smooth] = {}
iteration_number_new = iteration_number
ecmi_path = os.path.join(wdir, 'it_{}'.format(iteration_number_new + 1), 'ecmi.out')
smv_path = os.path.join(wdir, 'it_{}'.format(iteration_number_new + 1), 'smv.out')
while not os.path.isfile(ecmi_path) or not os.path.isfile(smv_path):
iteration_number_new -= 1
ecmi_path = os.path.join(wdir, 'it_{}'.format(iteration_number_new + 1), 'ecmi.out')
smv_path = os.path.join(wdir, 'it_{}'.format(iteration_number_new + 1), 'smv.out')
print('WARNING: Iteration number lowered by 1:', iteration_number_new)
if iteration_number_new <= 1:
break
if iteration_number_new <= 1:
continue
else:
iteration_number = iteration_number_new
#vgrid, gridn, griddelta, gridstart = read_vgrid(vgrid_path)
#vgrid_ref = read_vgrid(os.path.join(wdir, 'vgridsref.in'))[0]
norm, dampnorm, smoothnorm = calc_norm(wdir, iteration_number)
try:
fpath = os.path.join(wdir, 'residuals.dat')
chi = float(subprocess.check_output(['tail', fpath]).split()[-1])
except Exception as e:
print(e)
chi = np.nan
results[smooth][wdir] = {'dampnorm': dampnorm, 'smoothnorm': smoothnorm,
'norm': norm, 'chi': chi, 'damp': damp}
#print some output
for smooth, result in results.items():
print('Smoothing:', smooth)
for wdir, item in result.items():
print(item['chi'], item['norm'])
print(20*'#')
if fname_out:
with open(fname_out, 'w') as outfile:
json.dump(results, outfile)
return results
def quadratic_function(x, a, b, c):
return a * x ** 2 + b * x + c
def one_over_x(x, a, b, c):
return a / (x - b) + c
def exp_func(x, a, b, c):
return a * np.exp(-b * x) + c
def plot_tradeoff(fname_in, fix='smooth', plot_norm='both', min_smooth=0, min_damp=0, max_smooth=1e6, max_damp=1e6):
with open(fname_in, 'r') as infile:
results_smooth = json.load(infile)
lines = ["-", "--", "-.", ":"]
linecycler = cycle(lines)
# array will be built for each line: (smooth, damp, norm, chi)
plot_values = []
for smooth, result in results_smooth.items():
for item in result.values():
smooth = float(smooth)
damping = item['damp']
if smooth < min_smooth or damping < min_damp or smooth > max_smooth or damping > max_damp:
continue
plot_values.append(np.array([smooth, damping, item[plot_norm], item['chi']]))
plot_values = np.array(plot_values)
column_index = {'smooth': 0, 'damp': 1}
keys = np.unique(plot_values[:, column_index[fix]])
names = {'smooth': 'Smoothing', 'damp': 'Damping'}
for key in keys:
plot_line = plot_values[plot_values[:, column_index[fix]] == key]
second_index = column_index['smooth'] if fix == 'damp' else column_index['damp']
plot_line = np.array(sorted(plot_line, key=lambda x: x[second_index]))
norms = plot_line[:, 2]
chis = plot_line[:, 3]
#text = [str(item) for item in plot_line[:, second_index]]
x = np.linspace(min(norms), max(norms), num=100)
#popt, pcov = curve_fit(one_over_x, norms, chis, method='trf')#, bounds=[min(norms), max(norms)])
#fit_result = one_over_x(x, *popt)
#line = plt.plot(x, fit_result, ':', lw=0.8)[0]
fninfo = os.path.split(fname_in)[-1].replace('.json', '').split('_f')[-1]
label = '{}: {:g}'.format(names[fix], float(key))
line = plt.plot(norms, chis, linestyle=next(linecycler), lw=0.8, label=label)[0]
#coefs = poly.polyfit(norms, chis, 4)
#ffit = poly.polyval(x, coefs)
#line = plt.plot(x, ffit, ':', lw=0.8)[0]
#label = label='{}: {:g} (smgrad: {})'.format(names[fix], float(key), fninfo)
plt.plot(norms, chis, c=line.get_color(), marker='.', lw=0.)
#plt.text(norms, chis, text)
for item in plot_line:
plt.text(item[2], item[3], str(item[second_index]), horizontalalignment='left')
#plt.title('Plot of Misfit against Norm ({})'.format(plot_norm))
if __name__ == '__main__':
#calc_tradeoff('/data/AlpArray_Data/fmtomo/v5/tradeoff_curves/crust_included_grad_smooth_FIXED_dts_grad_1.5_sm*_damp*/',
# '/data/AlpArray_Data/various/alparray/tradeoff_v5_f1.5.json')
#calc_tradeoff('/data/AlpArray_Data/fmtomo/v5/tradeoff_curves/crust_included_grad_smooth_FIXED_dts_sm*_damp*/',
# '/data/AlpArray_Data/various/alparray/tradeoff_v5_f2.0.json')
fig = normed_figure(width_cm=10, ratio=1.)
#tradeoff_infiles = ['tradeoff_v4_f1.5.json', 'tradeoff_v4_f3.json', 'tradeoff_v4_f10.json']
tradeoff_infiles = ['tradeoff_v5_f2.0.json']#, 'tradeoff_v5_f1.5.json']
for infile in tradeoff_infiles:
infile = os.path.join('/data/AlpArray_Data/various/alparray/', infile)
plot_tradeoff(infile, fix='damp', plot_norm='norm')
plt.xlim([1900, 16200])
plt.ylim([2.72, 3.8])
plt.xlabel('Norm')
#plt.ylabel(r'Misfit($\frac{\chi^2}{N}$)')
plt.ylabel(r'Misfit($\chi^2/N$)')
#plt.title('Tradeoff curve Misfit vs Norm. Numbers in plot show smoothing values.')
plt.legend()
#plt.show()
plt.savefig('/data/AlpArray_Data/sciebo/AlpArray_home/pictures/paper_II/tradeoff.pdf', dpi=300)
@@ -0,0 +1,63 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import json
from pylot.tomography.fmtomo_tools.fmtomo_grid_utils import read_vgrid, write_vtk, calculate_differences_grid, write_vgrid
def visualize_frechet_derivative(fname_vgrid, fname_frechet, fname_out_vtk=None, fname_out_json=None,
fname_out_vgrid=None, diff_model=None, fname_vgrid_ref=None):
vgrid, gridN, gridDelta, gridStart = read_vgrid(fname_vgrid, inv_index_frechet=True)
if diff_model and fname_vgrid_ref:
raise OverflowError('Cannot have both parameters set, diff_model and fname_vgrid_ref')
if diff_model:
vgrid = calculate_differences_grid(vgrid, earth_model=diff_model)
if fname_vgrid_ref:
vgrid_ref, gridN_ref, gridDelta_ref, gridStart_ref = read_vgrid(fname_vgrid_ref, inv_index_frechet=False)
grid_check = (gridN == gridN_ref,
compare_tuple(gridDelta, gridDelta_ref),
compare_tuple(gridStart, gridStart_ref))
assert(all(grid_check), 'Missmatch ref grid size')
vps = np.array(vgrid['vps'])
vps_ref = np.array(vgrid_ref['vps'])
vps_rel = (vps - vps_ref) / vps_ref * 100.
print('Min/Max change {}/{}%'.format(min(vps_rel), max(vps_rel)))
vgrid['vps'] = list(vps_rel)
add_frechet(vgrid, fname_frechet)
if fname_out_vgrid:
write_vgrid(vgrid, gridN, gridDelta, gridStart, fname_out_vgrid)
if fname_out_vtk:
write_vtk(vgrid, fname_out_vtk, ['vps', 'frechs', 'grid_indices', 'hit_count'])
if fname_out_json:
with open(fname_out_vtk, 'w') as outfile:
json.dump(vgrid, outfile)
def add_frechet(vgrid, fname_frechet):
vgrid['frechs'] = list(np.zeros(len(vgrid['xs'])))
vgrid['hit_count'] = list(np.zeros(len(vgrid['xs'])))
with open(fname_frechet, 'r') as infile:
while True:
try:
n, source_id, m, k, n_pdev = [int(item) for item in infile.readline().split()]
except:
break
#print(n, source_ids, m, k, n_pdev)
for _ in range(n_pdev):
pdev_index, pdev = infile.readline().split()
pdev_index = int(pdev_index)
pdev = float(pdev)
vgrid_index = vgrid['inv_index'][pdev_index]
vgrid['frechs'][vgrid_index] += pdev
# hit by ray count
vgrid['hit_count'][vgrid_index] += 1
def compare_tuple(t1, t2, epsilon=1e-6):
for item1, item2 in zip(t1, t2):
if abs(item1 - item2) > epsilon:
return False
return True
File diff suppressed because it is too large Load Diff
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import glob
import os
import json
import numpy as np
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
from matplotlib.patches import Patch
#from cmcrameri import cm
from obspy.geodetics import gps2dist_azimuth
TRANSFORM = ccrs.PlateCarree()
def draw_schmid_faults(ax, fnin='schmidfaults.shp'):
reader = Reader(fnin)
lines = []
linestyles = []
for record in reader.records():
info = record.attributes
if info["fault_type"] == 1 or info["fault_type"] == 3:
linestyles.append('solid')
else:
linestyles.append('dashed')
line_arr = np.array(record.geometry.coords)
lines.append(line_arr)
# ax.add_collection(LineCollection(lines, linewidths=1.2, linestyles=linemarkers, colors='black', transform=ccrs.PlateCarree()))
for line, style in zip(lines, linestyles):
ax.plot(line[:, 0], line[:, 1], transform=TRANSFORM, ls=style, c='k', lw=1.2)
def draw_alcapadi_faults(ax, fnin='faults_alcapadi', color='black'):
reader = Reader(fnin)
lines = []
linestyles = []
linewidths = []
for record in reader.records():
info = record.attributes
if info["fault_type"] == 1:
linestyles.append('solid')
linewidths.append(.8)
elif info["fault_type"] == 2:
linestyles.append('solid')
linewidths.append(0.4)
else:
linestyles.append('dashed')
linewidths.append(.8)
line_arr = np.array(record.geometry.coords)
lines.append(line_arr)
for line, style, lwidth in zip(lines, linestyles, linewidths):
ax.plot(line[:, 0], line[:, 1], transform=TRANSFORM, ls=style, c='k', lw=lwidth)
def draw_alcapadi_model(ax, fnin='tect_units_alcapadi', alpha=0.2, add_legend=True):
reader = Reader(fnin)
color_dict = {"Adria accreted": (0.8, 0.7, 0.45, alpha),
"Adria autochton": (0.52, 0.32, 0.2, alpha),
"Europe accreted": (0.42, 0.67, 0.88, alpha),
"Flexural foredeep and graben fill": (0.6, 0.6, 0.6, alpha), # (1.0, 1.0, 220/255., alpha),
"Alpine Tethys": (0., 0.65, 0.3, alpha),
"Neotethys": (0.5, 0.8, 0.32, alpha)}
patches_legend = [Patch(color=value, label=key.capitalize()) for key, value in color_dict.items()]
for record in reader.records():
info = record.attributes
shape = record.geometry
color = color_dict.get(info['tect_unit'])
if not color:
color = (1.0, 1.0, 1.0, alpha)
ax.add_geometries(shape, crs=TRANSFORM, facecolor=color)
if add_legend:
ax.legend(handles=patches_legend, ncol=3, bbox_to_anchor=(0.5, -0.075), loc='center')
def init_cartopy_map(fig=None, draw_mapbound=True, fill_continents=False,
continents_color=None, mapbound_color=None, lakes_color=None, clon=None, clat=None):
if not fig:
fig = plt.figure()
#projection = ccrs.LambertConformal(central_longitude=clon, central_latitude=clat)
projection = ccrs.PlateCarree(central_longitude=clon)
ax = fig.add_subplot(111, projection=projection)
if fill_continents:
ax.add_feature(cartopy.feature.LAND, color=continents_color)
ax.add_feature(cartopy.feature.LAKES, color=lakes_color)
if draw_mapbound:
#ax.add_feature(cartopy.feature.OCEAN, color='w', linewidth=0.1)
ax.add_feature(cartopy.feature.BORDERS, linewidth=0.2, color='0.3')
ax.add_feature(cartopy.feature.COASTLINE, linewidth=0.3, color='0.3')
return ax
def make_map(fig=None, draw_model=False, model_legends=True, draw_faults=False, width=20,
height=14, clon=11, clat=46., draw_grid=True, continents='0.8', lakes='0.85', no_content=False,
no_fill=True, alpha_model=0.2, faults_color='k', station_file=None):
ax = init_cartopy_map(fig=fig, draw_mapbound=not no_content, fill_continents=not no_fill,
continents_color=continents, lakes_color=lakes, clon=clon, clat=clat)
if station_file:
with open(station_file, 'r') as fid:
stations = json.load(fid)
lons, lats = zip(*[(sta['longitude'], sta['latitude']) for sta in stations.values()])
ax.scatter(lons, lats, c='0.3', s=1, transform=ccrs.PlateCarree(), zorder=5, edgecolors='none', alpha=0.5)
# if draw_topo:
# draw_topo_model(basemap)
if draw_model:
fnin = '/home/marcel/sciebo/AlpArray_home/tectonic_maps_4dmb_2020_09_17/shape_files/tect_units_alcapadi'
draw_alcapadi_model(ax, fnin, add_legend=model_legends, alpha=alpha_model)
if draw_faults:
fnin = '/home/marcel/sciebo/AlpArray_home/tectonic_maps_4dmb_2020_09_17/shape_files/faults_alcapadi'
draw_alcapadi_faults(ax, fnin, faults_color)
# if not no_content:
# basemap.drawcountries(color=line_color, linewidth=0.2)
# basemap.drawcoastlines(color=line_color, linewidth=0.3)
if draw_grid:
gl = ax.gridlines(crs=TRANSFORM, draw_labels=True,
linewidth=0.5, color='gray', alpha=0.5, linestyle=':')
# dashes = [3, 6]
# parallels = list(np.arange(-90, 90, lgrid))
# parallels_small = [item for item in np.arange(-90, 90, sgrid) if not item in parallels]
# basemap.drawparallels(parallels_small, dashes=dashes, color=line_color, linewidth=0.1, zorder=7)
# basemap.drawparallels(parallels, dashes=[], color=line_color, linewidth=0.2, zorder=7, labels=[1, 1, 0, 0])
# meridians = list(np.arange(-180, 180, lgrid))
# meridians_small = [item for item in np.arange(-180, 180, sgrid) if not item in meridians]
# basemap.drawmeridians(meridians_small, dashes=dashes, color=line_color, linewidth=0.1, zorder=7)
# basemap.drawmeridians(meridians, dashes=[], color=line_color, linewidth=0.2, zorder=7, labels=[0, 0, 1, 1])
ax.set_extent([clon - width/2, clon + width/2, clat - height/2, clat + height/2])
return ax
def angle_marker(a1, a2, delta=1.):
a1 = np.deg2rad(a1)
a2 = np.deg2rad(a2)
delta = np.deg2rad(delta)
x_vals = [np.sin(angle) for angle in np.arange(a1, a2 + delta, delta)]
y_vals = [np.cos(angle) for angle in np.arange(a1, a2 + delta, delta)]
xy = zip(x_vals, y_vals)
#x1 = np.sin(a1)
#y1 = np.cos(a1)
#x2 = np.sin(a2)
#y2 = np.cos(a2)
marker = [(0, 0), *xy, (0, 0)]
return marker
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import glob
import numpy as np
import os
import json
import matplotlib.pyplot as plt
from obspy import Catalog, read_events
from pylot.core.util.dataprocessing import Metadata
def get_metadata(eventdir):
metadata_path = os.path.join(eventdir, 'resp')
metadata = Metadata(inventory=metadata_path, verbosity=0)
return metadata
def set_rc_params(textsize=7.):
plt.style.use('/home/marcel/solid_earth.mplstyle')
#plt.rcParams.update({'font.size': textsize,
# 'font.family': 'sans-serif'})
def normed_figure_ratio_width(width_cm, ratio):
width_inch = width_cm / 2.54
height_inch = width_inch / ratio
return width_inch, height_inch
def normed_figure_ratio_height(height_cm, ratio):
height_inch = height_cm / 2.54
width_inch = height_inch * ratio
return width_inch, height_inch
def normed_figure(width_cm=None, ratio=1.777):
#assert ((width_cm and not height_cm) or (height_cm and not width_cm)), 'Choose either of width or height!'
set_rc_params()
if width_cm:
fig = plt.figure(figsize=normed_figure_ratio_width(width_cm, ratio))
return fig
#elif height_cm:
# fig = plt.figure(figsize=normed_figure_ratio_height(height_cm, ratio))
return
def pol2cart(lat, lon, r):
x = r * np.cos(np.deg2rad(lat)) * np.cos(np.deg2rad(lon))
y = r * np.cos(np.deg2rad(lat)) * np.sin(np.deg2rad(lon))
z = r * np.sin(np.deg2rad(lat))
return x, y, z
def cart2pol(x, y, z):
r = np.sqrt(x**2 + y**2 + z**2)
theta = np.rad2deg(np.arccos(z/r))
phi = np.rad2deg(np.arctan2(y, x))
lat = 90. - theta
lon = phi
return lat, lon, r
def pol2cart_vector(lat, lon, north, east, r_comp):
if any(val is None for val in [north, east, r_comp]):
return None, None, None
phi = np.deg2rad(lon)
# change north components to common spherical coordinate convention
theta = np.deg2rad(90. - lat)
north *= -1
x = (np.sin(theta) * np.cos(phi) * r_comp +
np.cos(theta) * np.cos(phi) * north -
np.sin(phi) * east)
y = (np.sin(theta) * np.sin(phi) * r_comp +
np.cos(theta) * np.sin(phi) * north +
np.cos(phi) * east)
z = (np.cos(theta) * r_comp -
np.sin(theta) * north)
return x, y, z
def read_cat_obspy_dmt_database(databasedir, filemask):
infiles = glob.glob(os.path.join(databasedir, '*.a', filemask))
cat = Catalog()
nPicks = 0
for index, infile in enumerate(infiles):
print(f'Working on: {infile} ({index + 1}/{len(infiles)})')
event = read_events(infile)[0]
nPicks += len(event.picks)
cat += event
nEvents = len(cat)
print('Number of events: {} (filemask: {})'.format(nEvents, filemask))
print('Total # picks: {} ({:.2f} per event)'.format(nPicks, float(nPicks)/nEvents))
return cat
def get_event(cat, eventid):
for event in cat.events:
if event.resource_id.id.split('/')[-1] == eventid:
return event
def get_pick4station(picks, network_code, station_code, method='auto'):
for pick in picks:
if pick.waveform_id.network_code == network_code:
if pick.waveform_id.station_code == station_code:
if pick.method_id.id.endswith(method):
return pick
def delete_picks(picks, nwst_ids_delete):
''' Delete picks from list in picks containing ObsPy pick objects'''
for index, pick in list(reversed(list(enumerate(picks)))):
seed_id = pick.waveform_id.get_seed_string()
network, station = seed_id.split('.')[:2]
nwst_id = '{}.{}'.format(network, station)
if nwst_id in nwst_ids_delete:
picks.pop(index)
print('Removed pick: ', nwst_id)
return picks
def save_all_station_coordinates_dmt_database(dmt_database, fn_out):
'''
Get all station coordinates from dmt_database and write them (unique) to json outputfile
:param dmt_database:
:param fn_out:
:return:
'''
stations_dict = {}
eventdirs = glob.glob(os.path.join(dmt_database, '*.?'))
nEvents = len(eventdirs)
for index, eventdir in enumerate(eventdirs):
print('Working on event {} ({}/{})'.format(eventdir, index+1, nEvents))
metadata = get_metadata(eventdir)
current_stations_dict = metadata.get_all_coordinates()
for nwst_id, coords in current_stations_dict.items():
if not nwst_id in stations_dict.keys():
stations_dict[nwst_id] = coords
with open(fn_out, 'w') as outfile:
json.dump(stations_dict, outfile)
def get_metadata(eventdir):
metadata_path = os.path.join(eventdir, 'resp')
metadata = Metadata(inventory=metadata_path, verbosity=0)
return metadata
def get_coordinate_from_dist_baz(station_tmp, dist, baz, mode='deg'):
''' function copied from Andre'''
# station_tmp: [lon, lat]
if mode!='deg' and mode!='rad':
print('mode hast to be ether deg or rad!')
return None
else:
station = np.deg2rad(station_tmp)
epi_tmp=[0.,0.]
if mode=='deg':
dist = np.deg2rad(dist)
baz = np.deg2rad(baz)
az = baz - np.pi
if az < 0:
az += 2 * np.pi
epi_tmp[1] = np.arcsin(np.sin(station[1]) * np.cos(dist) - np.cos(station[1]) * np.sin(dist) * np.cos(az))
if (np.cos(dist) - np.sin(station[1]) * np.sin(epi_tmp[1])) / (np.cos(station[1]) * np.cos(epi_tmp[1])) >= 0.:
epi_tmp[0] = station[0] - np.arcsin(np.sin(dist) * np.sin(az) / np.cos(epi_tmp[1]))
else:
epi_tmp[0] = station[0] - np.pi + np.arcsin(np.sin(dist) * np.sin(az) / np.cos(epi_tmp[1]))
epi=np.rad2deg(epi_tmp)
return epi
+7 -12
View File
@@ -1,12 +1,7 @@
# This file may be used to create an environment using:
# $ conda create --name <env> --file <this file>
# platform: win-64
cartopy=0.20.2
matplotlib-base=3.3.4
numpy=1.22.3
obspy=1.3.0
pyqtgraph=0.12.4
pyside2=5.13.2
python=3.8.12
qt=5.12.9
scipy=1.8.0
Cartopy==0.23.0
joblib==1.4.2
obspy==1.4.1
pyaml==24.7.0
pyqtgraph==0.13.7
PySide2==5.15.8
pytest==8.3.2
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,99 @@
%This is a parameter input file for PyLoT/autoPyLoT.
%All main and special settings regarding data handling
%and picking are to be set here!
%Parameters are optimized for %extent data sets!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#main settings#
dmt_database_test #datapath# %data path
20171010_063224.a #eventID# %event ID for single event processing (* for all events found in database)
#invdir# %full path to inventory or dataless-seed file
PILOT #datastructure# %choose data structure
True #apverbose# %choose 'True' or 'False' for terminal output
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#NLLoc settings#
None #nllocbin# %path to NLLoc executable
None #nllocroot# %root of NLLoc-processing directory
None #phasefile# %name of autoPyLoT-output phase file for NLLoc
None #ctrfile# %name of autoPyLoT-output control file for NLLoc
ttime #ttpatter# %pattern of NLLoc ttimes from grid
AUTOLOC_nlloc #outpatter# %pattern of NLLoc-output file
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#parameters for seismic moment estimation#
3530.0 #vp# %average P-wave velocity
2500.0 #rho# %average rock density [kg/m^3]
300.0 0.8 #Qp# %quality factor for P waves (Qp*f^a); list(Qp, a)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#settings local magnitude#
1.0 1.0 1.0 #WAscaling# %Scaling relation (log(Ao)+Alog(r)+Br+C) of Wood-Anderson amplitude Ao [nm] If zeros are set, original Richter magnitude is calculated!
1.0 1.0 #magscaling# %Scaling relation for derived local magnitude [a*Ml+b]. If zeros are set, no scaling of network magnitude is applied!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#filter settings#
0.03 0.03 #minfreq# %Lower filter frequency [P, S]
0.5 0.5 #maxfreq# %Upper filter frequency [P, S]
4 4 #filter_order# %filter order [P, S]
bandpass bandpass #filter_type# %filter type (bandpass, bandstop, lowpass, highpass) [P, S]
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#common settings picker#
global #extent# %extent of array ("local", "regional" or "global")
-100.0 #pstart# %start time [s] for calculating CF for P-picking (if TauPy: seconds relative to estimated onset)
50.0 #pstop# %end time [s] for calculating CF for P-picking (if TauPy: seconds relative to estimated onset)
-50.0 #sstart# %start time [s] relative to P-onset for calculating CF for S-picking
50.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking
True #use_taup# %use estimated traveltimes from TauPy for calculating windows for CF
ak135 #taup_model# %Define TauPy model for traveltime estimation. Possible values: 1066a, 1066b, ak135, ak135f, herrin, iasp91, jb, prem, pwdk, sp6
P,Pdiff,S,SKS #taup_phases# %Specify possible phases for TauPy (comma separated). See Obspy TauPy documentation for possible values.
0.03 0.5 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz]
0.01 0.5 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz]
0.03 0.5 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz]
0.01 0.5 #bph2# %lower/upper corner freq. of second band pass filter z-comp. [Hz]
#special settings for calculating CF#
%!!Edit the following only if you know what you are doing!!%
#Z-component#
HOS #algoP# %choose algorithm for P-onset determination (HOS, ARZ, or AR3)
300.0 #tlta# %for HOS-/AR-AIC-picker, length of LTA window [s]
4 #hosorder# %for HOS-picker, order of Higher Order Statistics
2 #Parorder# %for AR-picker, order of AR process of Z-component
16.0 #tdet1z# %for AR-picker, length of AR determination window [s] for Z-component, 1st pick
10.0 #tpred1z# %for AR-picker, length of AR prediction window [s] for Z-component, 1st pick
12.0 #tdet2z# %for AR-picker, length of AR determination window [s] for Z-component, 2nd pick
6.0 #tpred2z# %for AR-picker, length of AR prediction window [s] for Z-component, 2nd pick
0.001 #addnoise# %add noise to seismogram for stable AR prediction
60.0 5.0 20.0 12.0 #tsnrz# %for HOS/AR, window lengths for SNR-and slope estimation [tnoise, tsafetey, tsignal, tslope] [s]
50.0 #pickwinP# %for initial AIC pick, length of P-pick window [s]
30.0 #Precalcwin# %for HOS/AR, window length [s] for recalculation of CF (relative to 1st pick)
2.0 #aictsmooth# %for HOS/AR, take average of samples for smoothing of AIC-function [s]
2.0 #tsmoothP# %for HOS/AR, take average of samples in this time window for smoothing CF [s]
0.006 #ausP# %for HOS/AR, artificial uplift of samples (aus) of CF (P)
2.0 #nfacP# %for HOS/AR, noise factor for noise level determination (P)
#H-components#
ARH #algoS# %choose algorithm for S-onset determination (ARH or AR3)
12.0 #tdet1h# %for HOS/AR, length of AR-determination window [s], H-components, 1st pick
6.0 #tpred1h# %for HOS/AR, length of AR-prediction window [s], H-components, 1st pick
8.0 #tdet2h# %for HOS/AR, length of AR-determinaton window [s], H-components, 2nd pick
4.0 #tpred2h# %for HOS/AR, length of AR-prediction window [s], H-components, 2nd pick
4 #Sarorder# %for AR-picker, order of AR process of H-components
100.0 #Srecalcwin# %for AR-picker, window length [s] for recalculation of CF (2nd pick) (H)
195.0 #pickwinS# %for initial AIC pick, length of S-pick window [s]
60.0 10.0 30.0 12.0 #tsnrh# %for ARH/AR3, window lengths for SNR-and slope estimation [tnoise, tsafetey, tsignal, tslope] [s]
22.0 #aictsmoothS# %for AIC-picker, take average of samples in this time window for smoothing of AIC-function [s]
20.0 #tsmoothS# %for AR-picker, take average of samples for smoothing CF [s] (S)
0.001 #ausS# %for HOS/AR, artificial uplift of samples (aus) of CF (S)
2.0 #nfacS# %for AR-picker, noise factor for noise level determination (S)
#first-motion picker#
1 #minfmweight# %minimum required P weight for first-motion determination
3.0 #minFMSNR# %miniumum required SNR for first-motion determination
10.0 #fmpickwin# %pick window [s] around P onset for calculating zero crossings
#quality assessment#
0.1 0.2 0.4 0.8 #timeerrorsP# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for P
4.0 8.0 16.0 32.0 #timeerrorsS# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for S
0.005 #minAICPslope# %below this slope [counts/s] the initial P pick is rejected
1.1 #minAICPSNR# %below this SNR the initial P pick is rejected
0.002 #minAICSslope# %below this slope [counts/s] the initial S pick is rejected
1.3 #minAICSSNR# %below this SNR the initial S pick is rejected
20.0 #minsiglength# %length of signal part for which amplitudes must exceed noiselevel [s]
1.0 #noisefactor# %noiselevel*noisefactor=threshold
10.0 #minpercent# %required percentage of amplitudes exceeding threshold
0.1 #zfac# %P-amplitude must exceed at least zfac times RMS-S amplitude
100.0 #mdttolerance# %maximum allowed deviation of P picks from median [s]
50.0 #wdttolerance# %maximum allowed deviation from Wadati-diagram
25.0 #jackfactor# %pick is removed if the variance of the subgroup with the pick removed is larger than the mean variance of all subgroups times safety factor
+67
View File
@@ -0,0 +1,67 @@
import os
import pytest
from obspy import read_events
from autoPyLoT import autoPyLoT
class TestAutopickerGlobal():
def init(self):
self.params_infile = 'pylot_alparray_mantle_corr_stack_0.03-0.5.in'
self.test_event_dir = 'dmt_database_test'
self.fname_outfile_xml = os.path.join(
self.test_event_dir, '20171010_063224.a', 'PyLoT_20171010_063224.a_autopylot.xml'
)
# check if the input files exist
if not os.path.isfile(self.params_infile):
print(f'Test input file {os.path.abspath(self.params_infile)} not found.')
return False
if not os.path.exists(self.test_event_dir):
print(
f'Test event directory not found at location "{os.path.abspath(self.test_event_dir)}". '
f'Make sure to load it from the website first.'
)
return False
return True
def test_autopicker(self):
assert self.init(), 'Initialization failed due to missing input files.'
# check for output file in test directory and remove it if necessary
if os.path.isfile(self.fname_outfile_xml):
os.remove(self.fname_outfile_xml)
autoPyLoT(inputfile=self.params_infile, eventid='20171010_063224.a', obspyDMT_wfpath='processed')
# test for different known output files if they are identical or not
compare_pickfiles(self.fname_outfile_xml, 'PyLoT_20171010_063224.a_autopylot.xml', True)
compare_pickfiles(self.fname_outfile_xml, 'PyLoT_20171010_063224.a_saved_from_GUI.xml', True)
compare_pickfiles(self.fname_outfile_xml, 'PyLoT_20171010_063224.a_corrected_taup_times_0.03-0.5_P.xml', False)
def compare_pickfiles(pickfile1: str, pickfile2: str, samefile: bool = True) -> None:
"""
Compare the pick times and errors from two pick files.
Parameters:
pickfile1 (str): The path to the first pick file.
pickfile2 (str): The path to the second pick file.
samefile (bool): A flag indicating whether the two files are expected to be the same. Defaults to True.
Returns:
None
"""
cat1 = read_events(pickfile1)
cat2 = read_events(pickfile2)
picks1 = sorted(cat1[0].picks, key=lambda pick: str(pick.waveform_id))
picks2 = sorted(cat2[0].picks, key=lambda pick: str(pick.waveform_id))
pick_times1 = [pick.time for pick in picks1]
pick_times2 = [pick.time for pick in picks2]
pick_terrs1 = [pick.time_errors for pick in picks1]
pick_terrs2 = [pick.time_errors for pick in picks2]
# check if times and errors are identical or not depending on the samefile flag
assert (pick_times1 == pick_times2) is samefile, 'Pick times error'
assert (pick_terrs1 == pick_terrs2) is samefile, 'Pick time errors errors'
@@ -4,10 +4,8 @@
%Parameters are optimized for %extent data sets!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#main settings#
/home/darius #rootpath# %project path
alparray #datapath# %data path
waveforms_used #database# %name of data base
e0093.173.16 #eventID# %event ID for single event processing (* for all events found in database)
/home/darius/alparray/waveforms_used #datapath# %data path
e0093.173.16 #eventID# %event ID for single event processing (* for all events found in datapath)
/home/darius/alparray/metadata #invdir# %full path to inventory or dataless-seed file
PILOT #datastructure# %choose data structure
True #apverbose# %choose 'True' or 'False' for terminal output
@@ -43,6 +41,7 @@ global #extent# %extent of a
875.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking
False #use_taup# %use estimated traveltimes from TauPy for calculating windows for CF
IASP91 #taup_model# %define TauPy model for traveltime estimation. Possible values: 1066a, 1066b, ak135, ak135f, herrin, iasp91, jb, prem, pwdk, sp6
P,Pdiff,S,Sdiff #taup_phases# %Specify possible phases for TauPy (comma separated). See Obspy TauPy documentation for possible values.
0.01 0.1 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz]
0.001 0.5 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz]
0.01 0.5 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz]
@@ -4,10 +4,8 @@
%Parameters are optimized for %extent data sets!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#main settings#
/home/darius #rootpath# %project path
alparray #datapath# %data path
waveforms_used #database# %name of data base
e0093.173.16 #eventID# %event ID for single event processing (* for all events found in database)
/home/darius/alparray/waveforms_used #datapath# %data path
e0093.173.16 #eventID# %event ID for single event processing (* for all events found in datapath)
/home/darius/alparray/metadata #invdir# %full path to inventory or dataless-seed file
PILOT #datastructure# %choose data structure
True #apverbose# %choose 'True' or 'False' for terminal output
@@ -43,6 +41,7 @@ global #extent# %extent of a
875.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking
True #use_taup# %use estimated traveltimes from TauPy for calculating windows for CF
IASP91 #taup_model# %define TauPy model for traveltime estimation. Possible values: 1066a, 1066b, ak135, ak135f, herrin, iasp91, jb, prem, pwdk, sp6
P,Pdiff,S,Sdiff #taup_phases# %Specify possible phases for TauPy (comma separated). See Obspy TauPy documentation for possible values.
0.01 0.1 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz]
0.001 0.5 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz]
0.01 0.5 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz]
@@ -1,6 +1,7 @@
import os
import sys
import unittest
import pytest
import obspy
from obspy import UTCDateTime
@@ -105,7 +106,6 @@ class TestAutopickStation(unittest.TestCase):
# show complete diff when difference in results dictionaries are found
self.maxDiff = None
# @skip("Works")
def test_autopickstation_taupy_disabled_gra1(self):
expected = {
'P': {'picker': 'auto', 'snrdb': 15.405649120980094, 'weight': 0, 'Mo': None, 'marked': [], 'Mw': None,
@@ -121,8 +121,8 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.gra1, pickparam=self.pickparam_taupy_disabled,
metadata=(None, None))
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('GRA1', station)
def test_autopickstation_taupy_enabled_gra1(self):
@@ -140,8 +140,8 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.gra1, pickparam=self.pickparam_taupy_enabled,
metadata=self.metadata, origin=self.origin)
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('GRA1', station)
def test_autopickstation_taupy_disabled_gra2(self):
@@ -157,8 +157,8 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.gra2, pickparam=self.pickparam_taupy_disabled,
metadata=(None, None))
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('GRA2', station)
def test_autopickstation_taupy_enabled_gra2(self):
@@ -175,8 +175,8 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.gra2, pickparam=self.pickparam_taupy_enabled,
metadata=self.metadata, origin=self.origin)
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('GRA2', station)
def test_autopickstation_taupy_disabled_ech(self):
@@ -190,8 +190,8 @@ class TestAutopickStation(unittest.TestCase):
'fm': None, 'spe': None, 'channel': u'LHE'}}
with HidePrints():
result, station = autopickstation(wfstream=self.ech, pickparam=self.pickparam_taupy_disabled)
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('ECH', station)
def test_autopickstation_taupy_enabled_ech(self):
@@ -208,8 +208,8 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.ech, pickparam=self.pickparam_taupy_enabled,
metadata=self.metadata, origin=self.origin)
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('ECH', station)
def test_autopickstation_taupy_disabled_fiesa(self):
@@ -224,8 +224,8 @@ class TestAutopickStation(unittest.TestCase):
'fm': None, 'spe': None, 'channel': u'LHE'}}
with HidePrints():
result, station = autopickstation(wfstream=self.fiesa, pickparam=self.pickparam_taupy_disabled)
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('FIESA', station)
def test_autopickstation_taupy_enabled_fiesa(self):
@@ -242,8 +242,8 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.fiesa, pickparam=self.pickparam_taupy_enabled,
metadata=self.metadata, origin=self.origin)
self.assertDictContainsSubset(expected=expected['P'], actual=result['P'])
self.assertDictContainsSubset(expected=expected['S'], actual=result['S'])
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('FIESA', station)
def test_autopickstation_gra1_z_comp_missing(self):
@@ -272,7 +272,8 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=wfstream, pickparam=self.pickparam_taupy_disabled,
metadata=(None, None))
self.assertEqual(expected, result)
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
self.assertEqual('GRA1', station)
def test_autopickstation_a106_taupy_enabled(self):
@@ -290,7 +291,9 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.a106, pickparam=self.pickparam_taupy_enabled,
metadata=self.metadata, origin=self.origin)
self.assertEqual(expected, result)
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
def test_autopickstation_station_missing_in_metadata(self):
"""This station is not in the metadata, but Taupy is enabled. Taupy should exit cleanly and modify the starttime
@@ -311,8 +314,37 @@ class TestAutopickStation(unittest.TestCase):
with HidePrints():
result, station = autopickstation(wfstream=self.a005a, pickparam=self.pickparam_taupy_enabled,
metadata=self.metadata, origin=self.origin)
self.assertEqual(expected, result)
compare_dicts(expected=expected['P'], result=result['P'], hint='P-')
compare_dicts(expected=expected['S'], result=result['S'], hint='S-')
def run_dict_comparison(result, expected):
for key, expected_value in expected.items():
if isinstance(expected_value, dict):
run_dict_comparison(result[key], expected[key])
else:
res = result[key]
if isinstance(res, UTCDateTime) and isinstance(expected_value, UTCDateTime):
res = res.timestamp
expected_value = expected_value.timestamp
assert expected_value == pytest.approx(res), f'{key}: {expected_value} != {res}'
def compare_dicts(result, expected, hint=''):
try:
run_dict_comparison(result, expected)
except AssertionError:
raise AssertionError(f'{hint}Dictionaries not equal.'
f'\n\n<<Expected>>\n{pretty_print_dict(expected)}'
f'\n\n<<Result>>\n{pretty_print_dict(result)}')
def pretty_print_dict(dct):
retstr = ''
for key, value in sorted(dct.items(), key=lambda x: x[0]):
retstr += f"{key} : {value}\n"
return retstr
if __name__ == '__main__':
unittest.main()
+33
View File
@@ -0,0 +1,33 @@
import unittest
from pylot.core.io.phases import getQualitiesfromxml
class TestQualityFromXML(unittest.TestCase):
def setUp(self):
self.path = '.'
self.ErrorsP = [0.02, 0.04, 0.08, 0.16]
self.ErrorsS = [0.04, 0.08, 0.16, 0.32]
self.test0_result = [[0.0136956521739, 0.0126, 0.0101612903226, 0.00734848484849, 0.0135069444444,
0.00649659863946, 0.0129513888889, 0.0122747747748, 0.0119252873563, 0.0103947368421,
0.0092380952381, 0.00916666666667, 0.0104444444444, 0.0125333333333, 0.00904761904762,
0.00885714285714, 0.00911616161616, 0.0164166666667, 0.0128787878788, 0.0122756410256,
0.013653253667966917], [0.0239333333333, 0.0223791578953, 0.0217974304255],
[0.0504861111111, 0.0610833333333], [], [0.171029411765]], [
[0.0195, 0.0203623188406, 0.0212121212121, 0.0345833333333, 0.0196180555556,
0.0202536231884, 0.0200347222222, 0.0189, 0.0210763888889, 0.018275862069,
0.0213888888889, 0.0319791666667, 0.0205303030303, 0.0156388888889, 0.0192,
0.0231349206349, 0.023625, 0.02875, 0.0195512820513, 0.0239393939394, 0.0234166666667,
0.0174702380952, 0.0204151307995], [0.040314343081226646], [0.148555555556], [], []]
self.test1_result = [77.77777777777777, 11.11111111111111, 7.407407407407407, 0, 3.7037037037037037],\
[92.0, 4.0, 4.0, 0, 0]
def test_result_plotflag0(self):
self.assertEqual(getQualitiesfromxml(self.path, self.ErrorsP, self.ErrorsS, 0), self.test0_result)
def test_result_plotflag1(self):
self.assertEqual(getQualitiesfromxml(self.path, self.ErrorsP, self.ErrorsS, 1), self.test1_result)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,76 @@
import pytest
from obspy import read, Trace, UTCDateTime
from pylot.correlation.pick_correlation_correction import XCorrPickCorrection
class TestXCorrPickCorrection():
def setup(self):
self.make_test_traces()
self.make_test_picks()
self.t_before = 2.
self.t_after = 2.
self.cc_maxlag = 0.5
def make_test_traces(self):
# take first trace of test Stream from obspy
tr1 = read()[0]
# filter trace
tr1.filter('bandpass', freqmin=1, freqmax=20)
# make a copy and shift the copy by 0.1 s
tr2 = tr1.copy()
tr2.stats.starttime += 0.1
self.trace1 = tr1
self.trace2 = tr2
def make_test_picks(self):
# create an artificial reference pick on reference trace (trace1) and another one on the 0.1 s shifted trace
self.tpick1 = UTCDateTime('2009-08-24T00:20:07.7')
# shift the second pick by 0.2 s, the correction should be around 0.1 s now
self.tpick2 = self.tpick1 + 0.2
def test_slice_trace_okay(self):
self.setup()
xcpc = XCorrPickCorrection(UTCDateTime(), Trace(), UTCDateTime(), Trace(),
t_before=self.t_before, t_after=self.t_after, cc_maxlag=self.cc_maxlag)
test_trace = self.trace1
pick_time = self.tpick2
sliced_trace = xcpc.slice_trace(test_trace, pick_time)
assert ((sliced_trace.stats.starttime == pick_time - self.t_before - self.cc_maxlag / 2)
and (sliced_trace.stats.endtime == pick_time + self.t_after + self.cc_maxlag / 2))
def test_slice_trace_fails(self):
self.setup()
test_trace = self.trace1
pick_time = self.tpick1
with pytest.raises(ValueError):
xcpc = XCorrPickCorrection(UTCDateTime(), Trace(), UTCDateTime(), Trace(),
t_before=self.t_before + 20, t_after=self.t_after, cc_maxlag=self.cc_maxlag)
xcpc.slice_trace(test_trace, pick_time)
with pytest.raises(ValueError):
xcpc = XCorrPickCorrection(UTCDateTime(), Trace(), UTCDateTime(), Trace(),
t_before=self.t_before, t_after=self.t_after + 50, cc_maxlag=self.cc_maxlag)
xcpc.slice_trace(test_trace, pick_time)
def test_cross_correlation(self):
self.setup()
# create XCorrPickCorrection object
xcpc = XCorrPickCorrection(self.tpick1, self.trace1, self.tpick2, self.trace2, t_before=self.t_before,
t_after=self.t_after, cc_maxlag=self.cc_maxlag)
# execute correlation
correction, cc_max, uncert, fwfm = xcpc.cross_correlation(False, '', '')
# define awaited test result
test_result = (-0.09983091718314982, 0.9578431835689154, 0.0015285160561610929, 0.03625786256084631)
# check results
assert pytest.approx(test_result, rel=1e-6) == (correction, cc_max, uncert, fwfm)