├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── docs ├── Makefile ├── deploy_gh-pages.sh ├── gtag.js └── source │ ├── conf.py │ ├── custom_theme │ ├── static │ │ └── style.css │ └── theme.conf │ ├── index.rst │ ├── packages │ ├── index.rst │ ├── pySW4.cli.create_all_plots.rst │ ├── pySW4.cli.plot_image.rst │ ├── pySW4.cli.png2mp4.rst │ ├── pySW4.cli.rst │ ├── pySW4.headers.rst │ ├── pySW4.plotting.hillshade.rst │ ├── pySW4.plotting.image.rst │ ├── pySW4.plotting.png2mp4.rst │ ├── pySW4.plotting.rst │ ├── pySW4.plotting.utils.rst │ ├── pySW4.plotting.waveforms.rst │ ├── pySW4.postp.image.rst │ ├── pySW4.postp.rst │ ├── pySW4.postp.waveforms.rst │ ├── pySW4.prep.material_model.rst │ ├── pySW4.prep.rfileIO.rst │ ├── pySW4.prep.rst │ ├── pySW4.prep.source.rst │ ├── pySW4.prep.stations.rst │ ├── pySW4.sw4_metadata.rst │ ├── pySW4.utils.geo.rst │ ├── pySW4.utils.rst │ ├── pySW4.utils.spectral.rst │ ├── pySW4.utils.utils.rst │ └── tutorials.rst │ ├── static │ └── index.html │ └── templates │ ├── index.html │ └── layout.html ├── images ├── seismograms.png ├── shakemap.cycle=286.z=0.hmag.png └── shakemap.cycle=477.z=0.hmax.png ├── pySW4 ├── __init__.py ├── cli │ ├── __init__.py │ ├── create_all_plots.py │ ├── plot_image.py │ └── png2mp4.py ├── headers.py ├── plotting │ ├── __init__.py │ ├── hillshade.py │ ├── image.py │ ├── png2mp4.py │ ├── utils.py │ └── waveforms.py ├── postp │ ├── __init__.py │ ├── image.py │ └── waveforms.py ├── prep │ ├── __init__.py │ ├── material_model.py │ ├── rfileIO.py │ ├── source.py │ └── stations.py ├── sw4_metadata.py └── utils │ ├── __init__.py │ ├── geo.py │ ├── spectral.py │ └── utils.py ├── setup.py └── tutorials ├── advanced_plotting.ipynb ├── berkeley.sw4 ├── building_sw4_input.ipynb ├── geo.ipynb ├── read_sw4_output.ipynb ├── rfile.ipynb └── shade and color.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | docs/build 4 | .ipynb_checkpoints 5 | pysw4.docset* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.3" 5 | 6 | install: 7 | - pip install sphinx 8 | - pip install travis-sphinx 9 | 10 | script: 11 | - travis-sphinx build 12 | 13 | after_success: 14 | - travis-sphinx deploy -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ______ ____ __ 2 | ____ __ __/ ___/ | / / // / 3 | / __ \/ / / /\__ \| | /| / / // /_ 4 | / /_/ / /_/ /___/ /| |/ |/ /__ __/ 5 | / .___/\__, //____/ |__/|__/ /_/ 6 | /_/ /____/ 7 | 8 | 9 | 10 | pySW4: Python routines for interaction with SW4 11 | =============================================== 12 | 13 | pySW4 is an open-source project dedicated to provide a Python framework for working with numerical simulations of seismic-wave propagation with [SW4](https://geodynamics.org/resources/sw4/about) in all phases of the task. Preprocessing, post-processing and runtime visualization. It also provides several other tools for handling GeoTIFF files such as ASTER-GDEM tiles and others, plotting shaded-relief DEM maps and draping data over them, creating movies from image sequences, etc. 14 | 15 | The functionality is provided through 5 sub-packages which include pre- and post-processing routines including the [rfileIO](http://shaharkadmiel.github.io/pySW4/packages/pySW4.prep.rfileIO.html) library for interaction, reading and writing seismic models in the rfile format. 16 | 17 | There are some useful utilities for geodesic projections of data and for reading and writing GeoTIFF files. 18 | 19 | In the command line interface [scripts](http://shaharkadmiel.github.io/pySW4/packages/pySW4.cli.html), there are some quick and dirty plotting routines which can be run from the command line. It may be useful to run these scripts on the server-end while the computation is running in order to generate *pseudo-RunTime* visualization of the results. 20 | 21 | See the full [API documentation](http://shaharkadmiel.github.io/pySW4/) page and [Tutorial examples](http://shaharkadmiel.github.io/pySW4/packages/tutorials.html). 22 | 23 | Installation 24 | ------------ 25 | **conda** 26 | 27 | Installing ``pySW4`` from the conda-forge channel can be achieved by 28 | adding conda-forge to your channels with: 29 | 30 | $ conda config --add channels conda-forge 31 | 32 | Once the conda-forge channel has been enabled, ``pySW4`` can be 33 | installed with: 34 | 35 | $ conda install pysw4 36 | 37 | It is possible to list all of the versions of ``pySW4`` available on 38 | your platform with: 39 | 40 | $ conda search pysw4 --channel conda-forge 41 | 42 | **pip** 43 | 44 | You can install the repository directly from GitHub. Use this command to install from ``master``: 45 | 46 | $ pip install https://github.com/shaharkadmiel/pySW4/archive/master.zip 47 | 48 | To get the latest release version do:: 49 | 50 | $ pip install https://github.com/shaharkadmiel/pySW4/archive/v0.3.0.zip 51 | 52 | Add the ``--no-deps`` to forgo dependency upgrade ot downgrade. 53 | 54 | Quick and dirty plotting from the command line 55 | ---------------------------------------------- 56 | 57 | Plots and movies for a SW4 simulation run can be created using the 58 | `pySW4-create-plots` command line utility, e.g.: 59 | 60 | ```bash 61 | $ pySW4-create-plots -c /tmp/UH_01_simplemost.in -s UH1 -s UH2 -s UH3 \\ 62 | --pre-filt 0.05,0.1,100,200 --filter type=lowpass,freq=10,corners=4 \\ 63 | ~/data/stations/stationxml/*UH* ~/data/waveforms/*20100622214704* 64 | ``` 65 | 66 | ![Wavefield](/images/shakemap.cycle=286.z=0.hmag.png) 67 | ![PGV map](/images/shakemap.cycle=477.z=0.hmax.png) 68 | ![Seismograms](/images/seismograms.png) 69 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pySW4.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pySW4.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pySW4" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pySW4" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | 227 | 228 | # Additions made for auto generating and publishing documentation 229 | # Shahar Shani-Kadmiel, August, 2016 230 | 231 | .PHONY: deploy 232 | deploy: 233 | ./deploy_gh-pages.sh 234 | -------------------------------------------------------------------------------- /docs/deploy_gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 4 | 5 | DOCSDIR=$(pwd) 6 | TMPDIR=~/tmp_docs 7 | DSTDIR=$(cd $DOCSDIR/.. && pwd) 8 | 9 | echo 10 | echo "Preparing documentations for deployment to gh-pages..." 11 | echo "======================================================" 12 | echo 13 | 14 | echo "Removing $TMPDIR to make sure it is recreated fresh & clean." 15 | rm -rf $TMPDIR 16 | 17 | echo 18 | echo "Removing old data to prevent any rebuilding artifacts." 19 | rm -rf build 20 | 21 | echo "Remaking the docs..." 22 | make html 23 | 24 | echo "Adding Google Analytics (gtag.js) to index.html" 25 | sed -i '' '//r gtag.js' $DOCSDIR/build/html/index.html 26 | 27 | echo 28 | echo "Coping new docs to $TMPDIR and switching branch to gh-pages" 29 | cp -pRv build $TMPDIR 30 | 31 | echo 32 | git checkout gh-pages 33 | 34 | echo 35 | echo "Removing old data before moving html files of the new docs in" 36 | echo "from $TMPDIR/html" 37 | cd $DSTDIR 38 | rm -rfv .nojekyll .buildinfo _* *.html *.inv packages *.js # docs pySW4 39 | 40 | # cp -pRv $TMPDIR/html/{.[!.],}* $DSTDIR 41 | cp -pRv $DOCSDIR/build/html/{.[!.],}* $DSTDIR 42 | rm -rfv docs pySW4 43 | 44 | echo 45 | echo "Adding new doc files to be committed, committing changes" 46 | git add -A 47 | git commit -m "Generated gh-pages for `git log $CURRENT_BRANCH -1 --pretty=short --abbrev-commit`" && git push origin gh-pages 48 | 49 | echo 50 | echo "Switching back to the current branch : $CURRENT_BRANCH ..." 51 | git checkout $CURRENT_BRANCH 52 | 53 | echo 54 | echo "Putting the docs back in $DOCSDIR/build..." 55 | mkdir $DOCSDIR/build 56 | cp -pRv $TMPDIR/* $DOCSDIR/build 57 | 58 | echo 59 | echo "Cleaning up..." 60 | rm -rfv $TMPDIR 61 | 62 | echo 63 | echo "Done!" 64 | -------------------------------------------------------------------------------- /docs/gtag.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pySW4 documentation build configuration file 4 | 5 | import os 6 | import sys 7 | sys.path.insert(0, os.path.abspath('../../')) 8 | sys.path.insert(0, os.path.abspath('.')) 9 | 10 | # get the package version from from the main __init__ file. 11 | for line in open('../../pySW4/__init__.py'): 12 | if '__version__' in line: 13 | package_version = line.split()[-1] 14 | 15 | # -- General configuration ------------------------------------------------ 16 | 17 | # If your documentation needs a minimal Sphinx version, state it here. 18 | # 19 | # needs_sphinx = '1.0' 20 | 21 | # Add any Sphinx extension module names here, as strings. They can be 22 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 23 | # ones. 24 | extensions = [ 25 | 'sphinx.ext.intersphinx', 26 | 'sphinx.ext.autodoc', 27 | 'sphinx.ext.coverage', 28 | 'sphinx.ext.mathjax', 29 | 'sphinx.ext.viewcode', 30 | 'sphinx.ext.githubpages', 31 | # 'sphinx.ext.napoleon', 32 | 'numpydoc', 33 | 'sphinx.ext.autosummary', 34 | ] 35 | 36 | # numpydoc settings 37 | numpydoc_show_class_members = False 38 | 39 | 40 | # Napoleon settings 41 | # napoleon_google_docstring = True 42 | # napoleon_numpy_docstring = True 43 | # napoleon_include_init_with_doc = True 44 | # napoleon_include_private_with_doc = True 45 | # napoleon_include_special_with_doc = False 46 | # napoleon_use_admonition_for_examples = False 47 | # napoleon_use_admonition_for_notes = False 48 | # napoleon_use_admonition_for_references = False 49 | # napoleon_use_ivar = False 50 | # napoleon_use_param = True 51 | # napoleon_use_rtype = False 52 | 53 | # Add any paths that contain templates here, relative to this directory. 54 | templates_path = ['templates'] 55 | 56 | # The suffix(es) of source filenames. 57 | # You can specify multiple suffix as a list of string: 58 | # 59 | # source_suffix = ['.rst', '.md'] 60 | source_suffix = '.rst' 61 | 62 | # The encoding of source files. 63 | # 64 | # source_encoding = 'utf-8-sig' 65 | 66 | # The master toctree document. 67 | master_doc = 'index' 68 | 69 | # General information about the project. 70 | project = u'pySW4' 71 | copyright = u'2012-2016, Shahar Shani-Kadmiel, Omry Volk, Tobias Megies' 72 | author = u'Shahar Shani-Kadmiel, Omry Volk, Tobias Megies' 73 | 74 | # The version info for the project you're documenting, acts as replacement for 75 | # |version| and |release|, also used in various other places throughout the 76 | # built documents. 77 | # 78 | # The short X.Y version. 79 | version = '.'.join(package_version.split('.',2)) 80 | # The full version, including alpha/beta/rc tags. 81 | release = package_version 82 | 83 | # The language for content autogenerated by Sphinx. Refer to documentation 84 | # for a list of supported languages. 85 | # 86 | # This is also used if you do content translation via gettext catalogs. 87 | # Usually you set "language" from the command line for these cases. 88 | language = None 89 | 90 | # There are two options for replacing |today|: either, you set today to some 91 | # non-false value, then it is used: 92 | # 93 | # today = '' 94 | # 95 | # Else, today_fmt is used as the format for a strftime call. 96 | # 97 | # today_fmt = '%B %d, %Y' 98 | 99 | # List of patterns, relative to source directory, that match files and 100 | # directories to ignore when looking for source files. 101 | # This patterns also effect to html_static_path and html_extra_path 102 | exclude_patterns = [] 103 | 104 | # The reST default role (used for this markup: `text`) to use for all 105 | # documents. 106 | # 107 | # default_role = None 108 | 109 | # If true, '()' will be appended to :func: etc. cross-reference text. 110 | # 111 | # add_function_parentheses = True 112 | 113 | # If true, the current module name will be prepended to all description 114 | # unit titles (such as .. function::). 115 | # 116 | add_module_names = False 117 | 118 | # If true, sectionauthor and moduleauthor directives will be shown in the 119 | # output. They are ignored by default. 120 | # 121 | # show_authors = False 122 | 123 | # The name of the Pygments (syntax highlighting) style to use. 124 | pygments_style = 'sphinx' 125 | 126 | # A list of ignored prefixes for module index sorting. 127 | # modindex_common_prefix = [] 128 | 129 | # If true, keep warnings as "system message" paragraphs in the built documents. 130 | # keep_warnings = False 131 | 132 | # If true, `todo` and `todoList` produce output, else they produce nothing. 133 | todo_include_todos = False 134 | 135 | 136 | # -- Options for HTML output ---------------------------------------------- 137 | 138 | # The theme to use for HTML and HTML Help pages. See the documentation for 139 | # a list of builtin themes. 140 | # 141 | # html_theme = 'sphinxdoc' 142 | html_theme = 'custom_theme' 143 | html_theme_path = ['.'] 144 | 145 | # Theme options are theme-specific and customize the look and feel of a theme 146 | # further. For a list of options available for each theme, see the 147 | # documentation. 148 | # 149 | # html_theme_options = {} 150 | 151 | # Add any paths that contain custom themes here, relative to this directory. 152 | # html_theme_path = [] 153 | 154 | # The name for this set of Sphinx documents. 155 | # " v documentation" by default. 156 | # 157 | # html_title = u'pySW4 v0.2.0' 158 | 159 | # A shorter title for the navigation bar. Default is the same as html_title. 160 | # 161 | # html_short_title = None 162 | 163 | # The name of an image file (relative to this directory) to place at the top 164 | # of the sidebar. 165 | # 166 | # html_logo = None 167 | 168 | # The name of an image file (relative to this directory) to use as a favicon of 169 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 170 | # pixels large. 171 | # 172 | # html_favicon = None 173 | 174 | # Add any paths that contain custom static files (such as style sheets) here, 175 | # relative to this directory. They are copied after the builtin static files, 176 | # so a file named "default.css" will overwrite the builtin "default.css". 177 | html_static_path = ['static'] 178 | 179 | # Add any extra paths that contain custom files (such as robots.txt or 180 | # .htaccess) here, relative to this directory. These files are copied 181 | # directly to the root of the documentation. 182 | # 183 | # html_extra_path = [] 184 | 185 | # If not None, a 'Last updated on:' timestamp is inserted at every page 186 | # bottom, using the given strftime format. 187 | # The empty string is equivalent to '%b %d, %Y'. 188 | # 189 | # html_last_updated_fmt = None 190 | 191 | # If true, SmartyPants will be used to convert quotes and dashes to 192 | # typographically correct entities. 193 | # 194 | # html_use_smartypants = True 195 | 196 | # Custom sidebar templates, maps document names to template names. 197 | # 198 | html_sidebars = { 199 | '**': ['localtoc.html', 'relations.html', 'sourcelink.html', 200 | 'searchbox.html']} 201 | 202 | # Additional templates that should be rendered to pages, maps page names to 203 | # template names. 204 | # 205 | # html_additional_pages = {} 206 | 207 | # If false, no module index is generated. 208 | # 209 | # html_domain_indices = True 210 | 211 | # If false, no index is generated. 212 | # 213 | # html_use_index = True 214 | 215 | # If true, the index is split into individual pages for each letter. 216 | # 217 | # html_split_index = False 218 | 219 | # If true, links to the reST sources are added to the pages. 220 | # 221 | # html_show_sourcelink = True 222 | 223 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 224 | # 225 | # html_show_sphinx = True 226 | 227 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 228 | # 229 | # html_show_copyright = True 230 | 231 | # If true, an OpenSearch description file will be output, and all pages will 232 | # contain a tag referring to it. The value of this option must be the 233 | # base URL from which the finished HTML is served. 234 | # 235 | # html_use_opensearch = '' 236 | 237 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 238 | # html_file_suffix = None 239 | 240 | # Language to be used for generating the HTML full-text search index. 241 | # Sphinx supports the following languages: 242 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 243 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 244 | # 245 | # html_search_language = 'en' 246 | 247 | # A dictionary with options for the search language support, empty by default. 248 | # 'ja' uses this config value. 249 | # 'zh' user can custom change `jieba` dictionary path. 250 | # 251 | # html_search_options = {'type': 'default'} 252 | 253 | # The name of a javascript file (relative to the configuration directory) that 254 | # implements a search results scorer. If empty, the default will be used. 255 | # 256 | # html_search_scorer = 'scorer.js' 257 | 258 | # Output file base name for HTML help builder. 259 | htmlhelp_basename = 'pySW4doc' 260 | 261 | # -- Options for LaTeX output --------------------------------------------- 262 | 263 | latex_elements = { 264 | # The paper size ('letterpaper' or 'a4paper'). 265 | # 266 | # 'papersize': 'letterpaper', 267 | 268 | # The font size ('10pt', '11pt' or '12pt'). 269 | # 270 | # 'pointsize': '10pt', 271 | 272 | # Additional stuff for the LaTeX preamble. 273 | # 274 | # 'preamble': '', 275 | 276 | # Latex figure (float) alignment 277 | # 278 | # 'figure_align': 'htbp', 279 | } 280 | 281 | # Grouping the document tree into LaTeX files. List of tuples 282 | # (source start file, target name, title, 283 | # author, documentclass [howto, manual, or own class]). 284 | latex_documents = [ 285 | (master_doc, 'pySW4.tex', u'pySW4 Documentation', 286 | u'Shahar Shani-Kadmiel, Omry Volk, Tobias Megies', 'manual'), 287 | ] 288 | 289 | # The name of an image file (relative to this directory) to place at the top of 290 | # the title page. 291 | # 292 | # latex_logo = None 293 | 294 | # For "manual" documents, if this is true, then toplevel headings are parts, 295 | # not chapters. 296 | # 297 | # latex_use_parts = False 298 | 299 | # If true, show page references after internal links. 300 | # 301 | # latex_show_pagerefs = False 302 | 303 | # If true, show URL addresses after external links. 304 | # 305 | # latex_show_urls = False 306 | 307 | # Documents to append as an appendix to all manuals. 308 | # 309 | # latex_appendices = [] 310 | 311 | # It false, will not define \strong, \code, itleref, \crossref ... but only 312 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 313 | # packages. 314 | # 315 | # latex_keep_old_macro_names = True 316 | 317 | # If false, no module index is generated. 318 | # 319 | # latex_domain_indices = True 320 | 321 | 322 | # -- Options for manual page output --------------------------------------- 323 | 324 | # One entry per manual page. List of tuples 325 | # (source start file, name, description, authors, manual section). 326 | man_pages = [ 327 | (master_doc, 'pysw4', u'pySW4 Documentation', 328 | [author], 1) 329 | ] 330 | 331 | # If true, show URL addresses after external links. 332 | # 333 | # man_show_urls = False 334 | 335 | 336 | # -- Options for Texinfo output ------------------------------------------- 337 | 338 | # Grouping the document tree into Texinfo files. List of tuples 339 | # (source start file, target name, title, author, 340 | # dir menu entry, description, category) 341 | texinfo_documents = [ 342 | (master_doc, 'pySW4', u'pySW4 Documentation', 343 | author, 'pySW4', 'One line description of project.', 344 | 'Miscellaneous'), 345 | ] 346 | 347 | # Documents to append as an appendix to all manuals. 348 | # 349 | # texinfo_appendices = [] 350 | 351 | # If false, no module index is generated. 352 | # 353 | # texinfo_domain_indices = True 354 | 355 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 356 | # 357 | # texinfo_show_urls = 'footnote' 358 | 359 | # If true, do not generate a @detailmenu in the "Top" node's menu. 360 | # 361 | # texinfo_no_detailmenu = False 362 | 363 | 364 | # Example configuration for intersphinx: refer to the Python standard library. 365 | intersphinx_mapping = { 366 | 'python': ('https://docs.python.org/2.7/', None), 367 | 'numpy': ('https://docs.scipy.org/doc/numpy/', None), 368 | 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), 369 | 'matplotlib': ('http://matplotlib.org/', None), 370 | 'obspy': ('https://docs.obspy.org/', None), 371 | } -------------------------------------------------------------------------------- /docs/source/custom_theme/static/style.css: -------------------------------------------------------------------------------- 1 | @import url("sphinxdoc.css"); /* make sure to sync this with the base theme's css filename */ 2 | 3 | th { 4 | hyphens: none; 5 | background-color: whitesmoke; 6 | } 7 | 8 | body { 9 | margin: 0px 10px 0px 10px; 10 | } 11 | 12 | p.rubric { 13 | background-color: #afc1c4; 14 | } -------------------------------------------------------------------------------- /docs/source/custom_theme/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = sphinxdoc 3 | stylesheet = style.css 4 | pygments_style = sphinxdoc.css -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | =================== 4 | pySW4 documentation 5 | =================== 6 | 7 | Introduction 8 | ------------ 9 | pySW4 is an open-source project dedicated to provide a Python framework 10 | for working with numerical simulations of seismic-wave propagation with 11 | SW4 in all phases of the task (preprocessing, post-processing and 12 | runtime visualization). The functionality is provided through 5 13 | sub-packages which include pre- and post-processing routines including 14 | the :mod:`~pySW4.prep.rfileIO` library for interaction, reading and 15 | writing seismic models in the rfile format. 16 | 17 | There are some useful utilities for geodesic projections of data and 18 | for reading and writing GeoTIFF files. 19 | 20 | In the command line interface scrips, there are some quick 21 | and dirty plotting routines which can be run from the command 22 | line. It may be useful to run these scrips on the server-end while the 23 | computation is running in order to generate *pseudo-RunTime* 24 | visualization of the results. 25 | 26 | Installation 27 | ------------ 28 | **conda** 29 | 30 | Installing ``pySW4`` from the conda-forge channel can be achieved by 31 | adding conda-forge to your channels with:: 32 | 33 | conda config --add channels conda-forge 34 | 35 | Once the conda-forge channel has been enabled, ``pySW4`` can be 36 | installed with:: 37 | 38 | conda install pysw4 39 | 40 | It is possible to list all of the versions of ``pySW4`` available on 41 | your platform with:: 42 | 43 | conda search pysw4 --channel conda-forge 44 | 45 | **pip** 46 | 47 | You can install the repository directly from GitHub. Use this command to install from ``master``:: 48 | 49 | pip install https://github.com/shaharkadmiel/pySW4/archive/master.zip 50 | 51 | To get the latest release version do:: 52 | 53 | pip install https://github.com/shaharkadmiel/pySW4/archive/v0.3.0.zip 54 | 55 | Add the ``--no-deps`` to forgo dependency upgrade ot downgrade. 56 | 57 | Documentation and tutorials 58 | --------------------------- 59 | .. toctree:: 60 | :maxdepth: 1 61 | 62 | packages/index 63 | packages/tutorials 64 | 65 | Indices and tables 66 | ================== 67 | 68 | * :ref:`genindex` 69 | * :ref:`modindex` 70 | * :ref:`search` -------------------------------------------------------------------------------- /docs/source/packages/index.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | pySW4 Library Reference 3 | ======================= 4 | 5 | .. automodule:: pySW4 6 | :members: 7 | :undoc-members: 8 | :private-members: 9 | :show-inheritance: 10 | 11 | The functionality is provided through the following sub-packages: 12 | ----------------------------------------------------------------- 13 | 14 | .. autosummary:: 15 | :toctree: . 16 | 17 | ~pySW4.cli 18 | ~pySW4.plotting 19 | ~pySW4.postp 20 | ~pySW4.prep 21 | ~pySW4.utils 22 | 23 | Submodules 24 | ---------- 25 | 26 | .. autosummary:: 27 | :toctree: . 28 | 29 | ~pySW4.headers 30 | ~pySW4.sw4_metadata 31 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.cli.create_all_plots.rst: -------------------------------------------------------------------------------- 1 | pySW4.cli.create_all_plots module 2 | ================================= 3 | 4 | .. automodule:: pySW4.cli.create_all_plots 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.cli.plot_image.rst: -------------------------------------------------------------------------------- 1 | pySW4.cli.plot_image module 2 | =========================== 3 | 4 | .. automodule:: pySW4.cli.plot_image 5 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.cli.png2mp4.rst: -------------------------------------------------------------------------------- 1 | pySW4.cli.png2mp4 module 2 | ======================== 3 | 4 | .. automodule:: pySW4.cli.png2mp4 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.cli.rst: -------------------------------------------------------------------------------- 1 | pySW4.cli package 2 | ================= 3 | 4 | .. automodule:: pySW4.cli 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | 10 | Submodules 11 | ---------- 12 | 13 | .. autosummary:: 14 | :toctree: . 15 | 16 | ~pySW4.cli.create_all_plots 17 | ~pySW4.cli.plot_image 18 | ~pySW4.cli.png2mp4 19 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.headers.rst: -------------------------------------------------------------------------------- 1 | pySW4.headers module 2 | ==================== 3 | 4 | .. automodule:: pySW4.headers 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.plotting.hillshade.rst: -------------------------------------------------------------------------------- 1 | pySW4.plotting.hillshade module 2 | =============================== 3 | 4 | .. automodule:: pySW4.plotting.hillshade 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.plotting.image.rst: -------------------------------------------------------------------------------- 1 | pySW4.plotting.image module 2 | =========================== 3 | 4 | .. automodule:: pySW4.plotting.image 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.plotting.png2mp4.rst: -------------------------------------------------------------------------------- 1 | pySW4.plotting.png2mp4 module 2 | ============================= 3 | 4 | .. automodule:: pySW4.plotting.png2mp4 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.plotting.rst: -------------------------------------------------------------------------------- 1 | pySW4.plotting package 2 | ====================== 3 | 4 | .. automodule:: pySW4.plotting 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | 10 | Submodules 11 | ---------- 12 | 13 | .. autosummary:: 14 | :toctree: . 15 | 16 | ~pySW4.plotting.hillshade 17 | ~pySW4.plotting.image 18 | ~pySW4.plotting.png2mp4 19 | ~pySW4.plotting.utils 20 | ~pySW4.plotting.waveforms 21 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.plotting.utils.rst: -------------------------------------------------------------------------------- 1 | pySW4.plotting.utils module 2 | =========================== 3 | 4 | .. automodule:: pySW4.plotting.utils 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.plotting.waveforms.rst: -------------------------------------------------------------------------------- 1 | pySW4.plotting.waveforms module 2 | =============================== 3 | 4 | .. automodule:: pySW4.plotting.waveforms 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.postp.image.rst: -------------------------------------------------------------------------------- 1 | pySW4.postp.image module 2 | ======================== 3 | 4 | .. automodule:: pySW4.postp.image 5 | :members: 6 | :private-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.postp.rst: -------------------------------------------------------------------------------- 1 | pySW4.postp package 2 | =================== 3 | 4 | .. automodule:: pySW4.postp 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | 10 | Submodules 11 | ---------- 12 | 13 | .. autosummary:: 14 | :toctree: . 15 | 16 | ~pySW4.postp.image 17 | ~pySW4.postp.waveforms 18 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.postp.waveforms.rst: -------------------------------------------------------------------------------- 1 | pySW4.postp.waveforms module 2 | ============================ 3 | 4 | .. automodule:: pySW4.postp.waveforms 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.prep.material_model.rst: -------------------------------------------------------------------------------- 1 | pySW4.prep.material_model module 2 | ================================ 3 | 4 | .. automodule:: pySW4.prep.material_model 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.prep.rfileIO.rst: -------------------------------------------------------------------------------- 1 | pySW4.prep.rfileIO module 2 | ========================= 3 | 4 | .. automodule:: pySW4.prep.rfileIO 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.prep.rst: -------------------------------------------------------------------------------- 1 | pySW4.prep package 2 | ================== 3 | 4 | .. automodule:: pySW4.prep 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | 10 | Submodules 11 | ---------- 12 | 13 | .. autosummary:: 14 | :toctree: . 15 | 16 | ~pySW4.prep.material_model 17 | ~pySW4.prep.rfileIO 18 | ~pySW4.prep.source 19 | ~pySW4.prep.stations 20 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.prep.source.rst: -------------------------------------------------------------------------------- 1 | pySW4.prep.source module 2 | ======================== 3 | 4 | .. automodule:: pySW4.prep.source 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.prep.stations.rst: -------------------------------------------------------------------------------- 1 | pySW4.prep.stations module 2 | ========================== 3 | 4 | .. automodule:: pySW4.prep.stations 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.sw4_metadata.rst: -------------------------------------------------------------------------------- 1 | pySW4.sw4_metadata module 2 | ========================= 3 | 4 | .. automodule:: pySW4.sw4_metadata 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.utils.geo.rst: -------------------------------------------------------------------------------- 1 | pySW4.utils.geo module 2 | ====================== 3 | 4 | .. automodule:: pySW4.utils.geo 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.utils.rst: -------------------------------------------------------------------------------- 1 | pySW4.utils package 2 | =================== 3 | 4 | .. automodule:: pySW4.utils 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | 10 | Submodules 11 | ---------- 12 | 13 | .. autosummary:: 14 | :toctree: . 15 | 16 | ~pySW4.utils.geo 17 | ~pySW4.utils.spectral 18 | ~pySW4.utils.utils 19 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.utils.spectral.rst: -------------------------------------------------------------------------------- 1 | pySW4.utils.spectral module 2 | =========================== 3 | 4 | .. automodule:: pySW4.utils.spectral 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/pySW4.utils.utils.rst: -------------------------------------------------------------------------------- 1 | pySW4.utils.utils module 2 | ======================== 3 | 4 | .. automodule:: pySW4.utils.utils 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/packages/tutorials.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | pySW4 Tutorials 3 | =============== 4 | 5 | This section includes IPython Notebooks with documented examples of 6 | pySW4's capabilities. Some of the notebooks process output from the 7 | ``sw4-v1.1/examples/rfile`` example, which uses the USGS Bay Area 8 | Velocity Model. The original input file can be found in the sw4-v1.1/examples/rfile directory, also on `SW4's GitHub repository 9 | `_. However to generate the output used in these tutorial notebooks run the simulation again with `this `_ input file. 10 | 11 | 12 | 1. `Preparing an SW4 simulation `_ 13 | 2. `Reading SW4 output `_ 14 | 3. `Advanced plotting with SW4 `_ 15 | 4. `Reading and writing rfiles with rfileIO `_ 16 | 5. `Making DEMs projections and reprojections `_ 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/source/static/index.html: -------------------------------------------------------------------------------- 1 | nothing here 2 | -------------------------------------------------------------------------------- /docs/source/templates/index.html: -------------------------------------------------------------------------------- 1 | nothing here 2 | -------------------------------------------------------------------------------- /docs/source/templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block rootrellink %} 3 |
  • View pySW4 on GitHub |
  • 4 | {{ super() }} 5 | {% endblock %} -------------------------------------------------------------------------------- /images/seismograms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaharkadmiel/pySW4/e0e690bef4925ba857fa9939b9c2d8b521539894/images/seismograms.png -------------------------------------------------------------------------------- /images/shakemap.cycle=286.z=0.hmag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaharkadmiel/pySW4/e0e690bef4925ba857fa9939b9c2d8b521539894/images/shakemap.cycle=286.z=0.hmag.png -------------------------------------------------------------------------------- /images/shakemap.cycle=477.z=0.hmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaharkadmiel/pySW4/e0e690bef4925ba857fa9939b9c2d8b521539894/images/shakemap.cycle=477.z=0.hmax.png -------------------------------------------------------------------------------- /pySW4/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pySW4: Python routines for interaction with SW4 4 | =============================================== 5 | 6 | **pySW4** is an open-source project dedicated to provide a Python 7 | framework for working with numerical simulations of seismic-wave 8 | propagation with SW4 in all phases of the task (preprocessing, 9 | post-processing and runtime visualization). 10 | 11 | :author: 12 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 13 | 14 | Omry Volk (omryv@post.bgu.ac.il) 15 | 16 | Tobias Megies (megies@geophysik.uni-muenchen.de) 17 | 18 | :copyright: 19 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 20 | 21 | Omry Volk (omryv@post.bgu.ac.il) 22 | 23 | Tobias Megies (megies@geophysik.uni-muenchen.de) 24 | 25 | :license: 26 | This code is distributed under the terms of the 27 | GNU Lesser General Public License, Version 3 28 | (https://www.gnu.org/copyleft/lesser.html) 29 | """ 30 | from __future__ import absolute_import, print_function, division 31 | 32 | __version__ = '0.3.5' 33 | 34 | from . import utils 35 | from . import plotting 36 | from .postp import read_image, read_stations 37 | 38 | from . import prep 39 | 40 | from .sw4_metadata import Inputfile, Outputfile, read_metadata 41 | 42 | from .plotting.utils import set_matplotlib_rc_params 43 | set_matplotlib_rc_params() 44 | -------------------------------------------------------------------------------- /pySW4/cli/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Command line scripts of pySW4. 4 | """ 5 | from __future__ import absolute_import, print_function, division 6 | -------------------------------------------------------------------------------- /pySW4/cli/create_all_plots.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Create all plots for an SW4 simulation run. 4 | """ 5 | from __future__ import absolute_import, print_function, division 6 | 7 | from argparse import ArgumentParser 8 | import warnings 9 | 10 | from obspy import read, Stream, read_inventory 11 | from obspy.core.inventory import Inventory 12 | 13 | from pySW4 import __version__ 14 | from pySW4.sw4_metadata import _decode_string_value 15 | from pySW4.plotting import create_image_plots, create_seismogram_plots 16 | 17 | 18 | def main(argv=None): 19 | parser = ArgumentParser(prog='pySW4-create-plots', 20 | description=__doc__.strip()) 21 | parser.add_argument('-V', '--version', action='version', 22 | version='%(prog)s ' + __version__) 23 | 24 | parser.add_argument('-c', '--input', dest='input_file', default=None, 25 | type=str, 26 | help='SW4 input file of simulation run.') 27 | 28 | parser.add_argument('-f', '--folder', dest='folder', default=None, 29 | type=str, 30 | help='Folder with SW4 simulation run output. ' 31 | 'Can be omitted if an input file is specified ' 32 | 'and output directory was not moved after the ' 33 | 'simulation.') 34 | 35 | parser.add_argument('--no-movies', dest='no_movies', action="store_true", 36 | default=False, help='Omits creation of mp4 movies.') 37 | 38 | parser.add_argument('-s', '--use-station', dest="used_stations", 39 | action="append", default=None, type=str, 40 | help='Station codes of stations to consider for ' 41 | 'seismogram plots. Can be specified multiple ' 42 | 'times. If not specified, all stations which ' 43 | 'have synthetic data output are used.') 44 | 45 | parser.add_argument('--water-level', dest="water_level", default=10, 46 | type=float, 47 | help='Water level used in instrument response ' 48 | 'removal of real/observed data (see ' 49 | 'obspy.core.trace.Trace.remove_response for ' 50 | 'details).') 51 | 52 | parser.add_argument('--pre-filt', dest="pre_filt", 53 | default=[0.05, 0.1, 100, 200], 54 | help='Frequency domain pre-filter used in instrument ' 55 | 'response removal of real/observed data (see ' 56 | 'obspy.core.trace.Trace.remove_response for ' 57 | 'details). Four comma-separated float values ' 58 | '(e.g. "0.1,0.5,40,80").') 59 | 60 | parser.add_argument('--filter', dest="filter_kwargs", default="", 61 | type=str, 62 | help='Filter to be applied on real/observed data ' 63 | 'after instrument response removal. ' 64 | 'Comma-separated kwargs as understood by ' 65 | 'obspy.core.trace.Trace.filter (e.g. ' 66 | '"type=bandpass,freqmin=1,freqmax=5").') 67 | 68 | parser.add_argument('real_data_files', nargs='+', 69 | help='Files with observed/real data (waveform files ' 70 | 'readable by obspy.read (e.g. MSEED, SAC, ..) ' 71 | 'and station metadata files readable by ' 72 | 'obspy.read_inventory (e.g. StationXML).') 73 | 74 | args = parser.parse_args(argv) 75 | 76 | if args.filter_kwargs: 77 | args.filter_kwargs = [ 78 | item.split("=") for item in args.filter_kwargs.split(",")] 79 | args.filter_kwargs = dict([ 80 | (k, _decode_string_value(v)) 81 | for k, v in args.filter_kwargs]) 82 | if isinstance(args.pre_filt, str): 83 | args.pre_filt = map(float, args.pre_filt.split(",")) 84 | 85 | st = Stream() 86 | inv = Inventory(networks=[], source="") 87 | msg = ("Could not read real/observed data file using obspy.read or " 88 | "obspy.read_inventory: '{}'") 89 | for f in args.real_data_files: 90 | try: 91 | inv += read_inventory(f) 92 | except: 93 | pass 94 | else: 95 | continue 96 | try: 97 | st += read(f) 98 | except: 99 | pass 100 | else: 101 | continue 102 | warnings.warn(msg.format(f)) 103 | 104 | create_image_plots(input_file=args.input_file, 105 | movies=not args.no_movies) 106 | create_seismogram_plots(input_file=args.input_file, folder=args.folder, 107 | stream_observed=st, inventory=inv, 108 | used_stations=args.used_stations, 109 | water_level=args.water_level, 110 | pre_filt=args.pre_filt, 111 | filter_kwargs=args.filter_kwargs) 112 | 113 | 114 | if __name__ == "__main__": 115 | main() 116 | -------------------------------------------------------------------------------- /pySW4/cli/plot_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Plot a single SW4 image. 4 | 5 | .. rubric:: Instructions: 6 | 7 | This module can be called from the command line to plot a single SW4 8 | image. It can be usefull to run this on the server-end to generate 9 | quick-and-dirty plots for *pseudo-RunTime* visualization of the results. 10 | Wrap it in a *cronjob* that plots the image from the last timestep and 11 | you are set. 12 | 13 | Type ``pySW4-plot-image`` to get the help message. 14 | 15 | **Example** 16 | :: 17 | 18 | $ pySW4-plot-image -cmap jet \\ 19 | -format png 'results/berkeley.cycle=00000.z=0.p.sw4img' 20 | 21 | will save the SW4 image from the Berkeley rfile example, 22 | ``berkeley.cycle=00000.z=0.p.sw4img`` to a *.png* image, 23 | ``./berkeley.cycle=00000.z=0.p.png`` with the ``jet`` colormap. 24 | 25 | .. module:: plot_image 26 | 27 | :author: 28 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 29 | 30 | :copyright: 31 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 32 | 33 | :license: 34 | This code is distributed under the terms of the 35 | GNU Lesser General Public License, Version 3 36 | (https://www.gnu.org/copyleft/lesser.html) 37 | """ 38 | 39 | from __future__ import absolute_import, print_function, division 40 | 41 | import os 42 | import sys 43 | import argparse 44 | from matplotlib.pyplot import get_backend, switch_backend 45 | from pySW4.postp.image import read_image 46 | 47 | 48 | def main(argv=None): 49 | 50 | class DefaultHelpParser(argparse.ArgumentParser): 51 | def error(self, message): 52 | sys.stderr.write('error: {}\n'.format(message)) 53 | self.print_help() 54 | sys.exit(2) 55 | 56 | parser = DefaultHelpParser( 57 | prog='pySW4-plot-image', 58 | description='Plot all (or specific) patches in an .sw4img file.' 59 | ) 60 | 61 | parser.add_argument('-patches', action='store', default=None, 62 | help='Patches to plot. If None, all patches are ' 63 | 'plotted. Otherwise provide a sequence of ' 64 | 'integers in quotation marks. For example: ' 65 | '"0 1 3" will plot patches 0, 1, 3. (default: ' 66 | '%(default)s)', 67 | metavar='None') 68 | 69 | parser.add_argument('-stf', action='store', default='displacement', 70 | help='Source-time function type used to initiate ' 71 | 'wave propagation in the simulation. ' 72 | 'displacement or velocity. (default: ' 73 | '%(default)s)', 74 | metavar='displacement') 75 | 76 | parser.add_argument('-vmin', action='store', default=None, 77 | help='Manually set minimum of color scale. (default: ' 78 | '%(default)s)', 79 | metavar='None') 80 | 81 | parser.add_argument('-vmax', action='store', default=None, 82 | help='Manually set maximum of color scale. (default: ' 83 | '%(default)s)', 84 | metavar='None') 85 | 86 | parser.add_argument('-no_cb', action='store_false', 87 | help='Suppress colorbar along with the image.') 88 | 89 | parser.add_argument('-cmap', action='store', default=None, 90 | help='Matplotlib colormap. If None, a suitable ' 91 | 'colormap is automatically picked (default: ' 92 | '%(default)s)', 93 | metavar=None) 94 | 95 | parser.add_argument('-dpi', action='store', default=300, 96 | help='Resolution of the saved image (default: ' 97 | '%(default)s)', 98 | metavar=300) 99 | 100 | parser.add_argument('-format', action='store', default='pdf', 101 | help='Image format to save. {pdf, png, jpeg, ...} ' 102 | '(default: %(default)s)', 103 | metavar='pdf') 104 | 105 | parser.add_argument('-save_path', action='store', default='./', 106 | help='Path (relative or absolute) to where image is ' 107 | 'saved.(default: %(default)s)', 108 | metavar='./') 109 | 110 | parser.add_argument('imagefile', action='store', 111 | help='Path (relative or absolute) to the SW4 image.') 112 | args = parser.parse_args(argv) 113 | 114 | image = read_image(args.imagefile, stf=args.stf) 115 | 116 | backend = get_backend() 117 | try: 118 | switch_backend('AGG') 119 | if args.patches is not None: 120 | patches = [int(item) for item in args.patches.split()] 121 | else: 122 | patches = args.patches 123 | 124 | try: 125 | vmin = float(args.vmin) 126 | except TypeError: 127 | vmin = args.vmin 128 | 129 | try: 130 | vmax = float(args.vmax) 131 | except TypeError: 132 | vmax = args.vmax 133 | 134 | fig = image.plot(patches, vmin=vmin, vmax=vmax, 135 | colorbar=args.no_cb, cmap=args.cmap)[0] 136 | name, _ = os.path.splitext(os.path.basename(args.imagefile)) 137 | name = os.path.join(args.save_path, name + '.' + args.format) 138 | 139 | fig.savefig(name, bbox_inches='tight', dpi=args.dpi) 140 | print('Saved {} ===> {}.'.format(args.imagefile, name)) 141 | finally: 142 | switch_backend(backend) 143 | 144 | if __name__ == "__main__": 145 | main() 146 | -------------------------------------------------------------------------------- /pySW4/cli/png2mp4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Convert a sequencial set of .png images to a .mp4 movie 4 | 5 | .. module:: png2mp4 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | :copyright: 11 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 12 | 13 | :license: 14 | This code is distributed under the terms of the 15 | GNU Lesser General Public License, Version 3 16 | (https://www.gnu.org/copyleft/lesser.html) 17 | """ 18 | 19 | from __future__ import absolute_import, print_function, division 20 | 21 | import sys 22 | import argparse 23 | from pySW4.plotting.png2mp4 import png2mp4 24 | 25 | 26 | def main(argv=None): 27 | 28 | class DefaultHelpParser(argparse.ArgumentParser): 29 | def error(self, message): 30 | sys.stderr.write('error: {}\n'.format(message)) 31 | self.print_help() 32 | sys.exit(2) 33 | 34 | parser = DefaultHelpParser( 35 | prog='png2mp4', 36 | description='Convert a sequencial set of ' 37 | '.png images to a .mp4 movie.') 38 | 39 | parser.add_argument('-crf', action='store', default=23, type=int, 40 | help='Constant Rate Factor, ranges from 0 to 51: A ' 41 | 'lower value is a higher quality and vise ' 42 | 'versa. The range is exponential, so increasing ' 43 | 'the CRF value +6 is roughly half the bitrate ' 44 | 'while -6 is roughly twice the bitrate. General ' 45 | 'usage is to choose the highest CRF value that ' 46 | 'still provides an acceptable quality. If the ' 47 | 'output looks good, then try a higher value and ' 48 | 'if it looks bad then choose a lower value. A ' 49 | 'subjectively sane range is 18 to 28. (default: ' 50 | '%(default)s)', 51 | metavar='23') 52 | 53 | parser.add_argument('-pts', action='store', default=1, type=float, 54 | help='Presentation TimeStamp, pts < 1 to speedup the ' 55 | 'video, pts > 1 to slow down the video. For ' 56 | 'example, 0.5 will double the speed and cut the ' 57 | 'video duration to half whereas 2 will slow the ' 58 | 'video down and double its duration. (default: ' 59 | '%(default)s)', 60 | metavar='1') 61 | 62 | parser.add_argument('-fps', action='store', default=30, type=float, 63 | help='Frames Per Second of the output video. I you ' 64 | 'speed a video up framesget dropped as ffmpeg ' 65 | 'is trying to fit more frames in less time. ' 66 | 'Try increasing the frame rate by the same ' 67 | 'factor used for pts. If youslow a video down, ' 68 | 'increasing the frame rate results in smoother ' 69 | 'videofor some reason. (default: %(default)s)', 70 | metavar='30') 71 | 72 | parser.add_argument('-q', action='store_true', 73 | help='Suppress any output.') 74 | 75 | parser.add_argument('-i', action='store', default='./', 76 | help='Path to where the sequential .png images are. ' 77 | '(default: %(default)s)', 78 | metavar='./') 79 | 80 | parser.add_argument('outfile', action='store', 81 | help='Name of the final .mp4 file.',) 82 | args = parser.parse_args(argv) 83 | 84 | png2mp4(args.outfile, args.i, args.crf, args.pts, args.fps, args.q) 85 | 86 | if __name__ == "__main__": 87 | main() 88 | -------------------------------------------------------------------------------- /pySW4/headers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Some dictionaries and data_types for SW4 output files. 4 | 5 | .. module:: headers 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | Omry Volk (omryv@post.bgu.ac.il) 11 | 12 | Tobias Megies (megies@geophysik.uni-muenchen.de) 13 | 14 | :copyright: 15 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 16 | 17 | Omry Volk (omryv@post.bgu.ac.il) 18 | 19 | Tobias Megies (megies@geophysik.uni-muenchen.de) 20 | 21 | :license: 22 | This code is distributed under the terms of the 23 | GNU Lesser General Public License, Version 3 24 | (https://www.gnu.org/copyleft/lesser.html) 25 | """ 26 | from __future__ import absolute_import, print_function, division 27 | 28 | import numpy as np 29 | from obspy.core.util import AttribDict 30 | 31 | # image header is 61 bytes 32 | IMAGE_HEADER_DTYPE = np.dtype([ 33 | ('precision' , np.int32), 34 | ('number of patches' , np.int32), 35 | ('time' , np.float64), 36 | ('plane' , np.int32), 37 | ('coordinate' , np.float64), 38 | ('mode' , np.int32), 39 | ('gridinfo' , np.int32), 40 | ('creation time' , 'S25')]) 41 | 42 | # patch header is 32 bytes 43 | PATCH_HEADER_DTYPE = np.dtype([ 44 | ('h' , np.float64), 45 | ('zmin' , np.float64), 46 | ('ib' , np.int32), 47 | ('ni' , np.int32), 48 | ('jb' , np.int32), 49 | ('nj' , np.int32)]) 50 | 51 | # patch header is 40 bytes 52 | VOLIMAGE_PATCH_HEADER_DTYPE = np.dtype([ 53 | ('h' , np.float64), 54 | ('zmin' , np.float64), 55 | ('ib' , np.int32), 56 | ('ni' , np.int32), 57 | ('jb' , np.int32), 58 | ('nj' , np.int32), 59 | ('kb' , np.int32), 60 | ('nk' , np.int32)]) 61 | 62 | IMAGE_MODE_COMMON = { 63 | 4 : {'name' : 'Density', 64 | 'symbol' : 'rho', 65 | 'cmap_type' : "sequential", 66 | 'unit' : 'kg/m^3'}, 67 | 5 : {'name' : 'Lambda', 68 | 'symbol' : 'lambda', 69 | 'cmap_type' : "sequential", 70 | 'unit' : 'Pa'}, 71 | 6 : {'name' : 'Mu', 72 | 'symbol' : 'mu', 73 | 'cmap_type' : "sequential", 74 | 'unit' : 'Pa'}, 75 | 7 : {'name' : 'P Wave Velocity', 76 | 'symbol' : 'Vp', 77 | 'cmap_type' : "sequential", 78 | 'unit' : 'm/s'}, 79 | 8 : {'name' : 'S Wave Velocity', 80 | 'symbol' : 'Vs', 81 | 'cmap_type' : "sequential", 82 | 'unit' : 'm/s'}, 83 | 16: {'name' : 'Latitude', 84 | 'symbol' : 'lat', 85 | 'cmap_type' : "sequential", 86 | 'unit' : 'degrees'}, 87 | 17: {'name' : 'Longitude', 88 | 'symbol' : 'lon', 89 | 'cmap_type' : "sequential", 90 | 'unit' : 'degrees'}, 91 | 18: {'name' : 'Topography', 92 | 'symbol' : 'topo', 93 | 'cmap_type' : "sequential", 94 | 'unit' : 'm'}, 95 | 19: {'name' : 'X', 96 | 'symbol' : 'x', 97 | 'cmap_type' : "sequential", 98 | 'unit' : 'm'}, 99 | 20: {'name' : 'X', 100 | 'symbol' : 'y', 101 | 'cmap_type' : "sequential", 102 | 'unit' : 'm'}, 103 | 21: {'name' : 'Z', 104 | 'symbol' : 'z', 105 | 'cmap_type' : "sequential", 106 | 'unit' : 'm'}, 107 | } 108 | IMAGE_MODE_DISPLACEMENT = { 109 | 1 : {'name' : 'X Displacement', 110 | 'symbol' : 'ux', 111 | 'cmap_type' : "divergent", 112 | 'unit' : 'm'}, 113 | 2 : {'name' : 'Y Displacement', 114 | 'symbol' : 'uy', 115 | 'cmap_type' : "divergent", 116 | 'unit' : 'm'}, 117 | 3 : {'name' : 'Z Displacement', 118 | 'symbol' : 'uz', 119 | 'cmap_type' : "divergent", 120 | 'unit' : 'm'}, 121 | 9 : {'name' : 'X Displacement (exact)', 122 | 'symbol' : 'ux ex', 123 | 'cmap_type' : "divergent", 124 | 'unit' : 'm'}, 125 | 10: {'name' : 'Y Displacement (exact)', 126 | 'symbol' : 'uy ex', 127 | 'cmap_type' : "divergent", 128 | 'unit' : 'm'}, 129 | 11: {'name' : 'Z Displacement (exact)', 130 | 'symbol' : 'uz ex', 131 | 'cmap_type' : "divergent", 132 | 'unit' : 'm'}, 133 | 12: {'name' : 'Divergence of Displacement', 134 | 'symbol' : 'div(u)', 135 | 'cmap_type' : "divergent", 136 | 'unit' : ''}, 137 | 13: {'name' : 'Curl of Displacement', 138 | 'symbol' : 'curl(u)', 139 | 'cmap_type' : "divergent", 140 | 'unit' : ''}, 141 | 14: {'name' : 'Divergence of Velocity', 142 | 'symbol' : 'div(du/dt)', 143 | 'cmap_type' : "divergent", 144 | 'unit' : 's^-1'}, 145 | 15: {'name' : 'Curl of Velocity', 146 | 'symbol' : 'curl(du/dt)', 147 | 'cmap_type' : "divergent", 148 | 'unit' : 's^-1'}, 149 | 22: {'name' : 'X Displacement Error', 150 | 'symbol' : 'ux error', 151 | 'cmap_type' : "sequential", 152 | 'unit' : ''}, 153 | 23: {'name' : 'Y Displacement Error', 154 | 'symbol' : 'uy error', 155 | 'cmap_type' : "sequential", 156 | 'unit' : ''}, 157 | 24: {'name' : 'Z Displacement Error', 158 | 'symbol' : 'uz error', 159 | 'cmap_type' : "sequential", 160 | 'unit' : ''}, 161 | 25: {'name' : 'Velocity Magnitude', 162 | 'altname' : '|Velocity|', 163 | 'symbol' : '|du/dt|', 164 | 'altsymbol' : 'magdudt', 165 | 'cmap_type' : "sequential", 166 | 'unit' : 'm/s'}, 167 | 26: {'name' : 'Horizontal Velocity Magnitude', 168 | 'symbol' : 'sqrt((dux/dt)^2 + (duy/dt)^2)', 169 | 'cmap_type' : "sequential", 170 | 'unit' : 'm/s'}, 171 | 27: {'name' : 'Peak Horizontal Velocity Magnitude', 172 | 'symbol' : 'max_t (sqrt((dux/dt)^2 + (duy/dt)^2))', 173 | 'cmap_type' : "sequential", 174 | 'unit' : 'm/s'}, 175 | 28: {'name' : 'Peak Vertical Velocity', 176 | 'symbol' : 'max_t |duz/dt|', 177 | 'cmap_type' : "sequential", 178 | 'unit' : 'm/s'}, 179 | 29: {'name' : 'Displacement Magnitude', 180 | 'altname' : '|Displacement|', 181 | 'symbol' : '|u|', 182 | 'altsymbol' : 'mag', 183 | 'cmap_type' : "sequential", 184 | 'unit' : 'm'}, 185 | 30: {'name' : 'Horizontal Displacement Magnitude', 186 | 'symbol' : 'sqrt(ux^2 + uy^2)', 187 | 'cmap_type' : "sequential", 188 | 'unit' : 'm'}, 189 | 31: {'name' : 'Peak Horizontal Displacement Magnitude', 190 | 'symbol' : 'max_t sqrt(ux^2 + uy^2)', 191 | 'cmap_type' : "sequential", 192 | 'unit' : 'm'}, 193 | 32: {'name' : 'Peak Vertical Displacement', 194 | 'symbol' : 'max_t |uz|', 195 | 'cmap_type' : "sequential", 196 | 'unit' : 'm'}, 197 | } 198 | IMAGE_MODE_DISPLACEMENT.update(IMAGE_MODE_COMMON) 199 | IMAGE_MODE_VELOCITY = { 200 | 1 : {'name' : 'X Velocity', 201 | 'symbol' : 'ux', 202 | 'cmap_type' : "divergent", 203 | 'unit' : 'm/s'}, 204 | 2 : {'name' : 'Y Velocity', 205 | 'symbol' : 'uy', 206 | 'cmap_type' : "divergent", 207 | 'unit' : 'm/s'}, 208 | 3 : {'name' : 'Z Velocity', 209 | 'symbol' : 'uz', 210 | 'cmap_type' : "divergent", 211 | 'unit' : 'm/s'}, 212 | 9 : {'name' : 'X Velocity (exact)', 213 | 'symbol' : 'ux ex', 214 | 'cmap_type' : "divergent", 215 | 'unit' : 'm/s'}, 216 | 10: {'name' : 'Y Velocity (exact)', 217 | 'symbol' : 'uy ex', 218 | 'cmap_type' : "divergent", 219 | 'unit' : 'm/s'}, 220 | 11: {'name' : 'Z Velocity (exact)', 221 | 'symbol' : 'uz ex', 222 | 'cmap_type' : "divergent", 223 | 'unit' : 'm/s'}, 224 | 12: {'name' : 'Divergence of Velocity', 225 | 'symbol' : 'div(u)', 226 | 'cmap_type' : "sequential", 227 | 'unit' : 's^-1'}, 228 | 13: {'name' : 'Curl of Velocity', 229 | 'symbol' : 'curl(u)', 230 | 'cmap_type' : "sequential", 231 | 'unit' : 's^-1'}, 232 | 14: {'name' : 'Divergence of Acceleration', 233 | 'symbol' : 'div(du/dt)', 234 | 'cmap_type' : "sequential", 235 | 'unit' : 's^-2'}, 236 | 15: {'name' : 'Curl of Acceleration', 237 | 'symbol' : 'curl(du/dt)', 238 | 'cmap_type' : "sequential", 239 | 'unit' : 's^-2'}, 240 | 22: {'name' : 'X Velocity Error', 241 | 'symbol' : 'ux error', 242 | 'cmap_type' : "sequential", 243 | 'unit' : ''}, 244 | 23: {'name' : 'Y Velocity Error', 245 | 'symbol' : 'uy error', 246 | 'cmap_type' : "sequential", 247 | 'unit' : ''}, 248 | 24: {'name' : 'Z Velocity Error', 249 | 'symbol' : 'uz error', 250 | 'cmap_type' : "sequential", 251 | 'unit' : ''}, 252 | 25: {'name' : 'Acceleration Magnitude', 253 | 'symbol' : '|du/dt|', 254 | 'cmap_type' : "sequential", 255 | 'unit' : 'm/s^2'}, 256 | 26: {'name' : 'Horizontal Acceleration Magnitude', 257 | 'symbol' : 'sqrt((dux/dt)^2 + (duy/dt)^2)', 258 | 'cmap_type' : "sequential", 259 | 'unit' : 'm/s^2'}, 260 | 27: {'name' : 'Peak Horizontal Acceleration Magnitude', 261 | 'symbol' : 'max_t (sqrt((dux/dt)^2 + (duy/dt)^2))', 262 | 'cmap_type' : "sequential", 263 | 'unit' : 'm/s^2'}, 264 | 28: {'name' : 'Peak Vertical Acceleration', 265 | 'symbol' : 'max_t |duz/dt|', 266 | 'cmap_type' : "sequential", 267 | 'unit' : 'm/s^2'}, 268 | 29: {'name' : 'Velocity Magnitude', 269 | 'symbol' : '|u|', 270 | 'cmap_type' : "sequential", 271 | 'unit' : 'm/s'}, 272 | 30: {'name' : 'Horizontal Velocity Magnitude', 273 | 'symbol' : 'sqrt(ux^2 + uy^2)', 274 | 'cmap_type' : "sequential", 275 | 'unit' : 'm/s'}, 276 | 31: {'name' : 'Peak Horizontal Velocity Magnitude', 277 | 'symbol' : 'max_t sqrt(ux^2 + uy^2)', 278 | 'cmap_type' : "sequential", 279 | 'unit' : 'm/s'}, 280 | 32: {'name' : 'Peak Vertical Velocity', 281 | 'symbol' : 'max_t |uz|', 282 | 'cmap_type' : "sequential", 283 | 'unit' : 'm/s'}, 284 | } 285 | IMAGE_MODE_VELOCITY.update(IMAGE_MODE_COMMON) 286 | 287 | IMAGE_PLANE = {0: 'x', 1: 'y', 2: 'z'} 288 | 289 | IMAGE_PRECISION = {4: np.float32, 8: np.float64} 290 | 291 | STF_ = { 292 | "Gaussian" : {'type' : 'velocity', 293 | 'fmax2f0' : 2.5, 294 | 'freq2f0' : 2 * np.pi, 295 | }, 296 | "GaussianInt" : {'type' : 'displacement', 297 | 'fmax2f0' : 2.5, 298 | 'freq2f0' : 2 * np.pi, 299 | }, 300 | "Ricker" : {'type' : 'velocity', 301 | 'fmax2f0' : 2.5, 302 | 'freq2f0' : 1.0, 303 | }, 304 | "RickerInt" : {'type' : 'velocity', 305 | 'fmax2f0' : 2.5, 306 | 'freq2f0' : 1.0, 307 | }, 308 | "Brune" : {'type' : 'displacement', 309 | 'fmax2f0' : 4.0, 310 | 'freq2f0' : 2 * np.pi, 311 | }, 312 | "BruneSmoothed" : {'type' : 'displacement', 313 | 'fmax2f0' : 4.0, 314 | 'freq2f0' : 2 * np.pi, 315 | }, 316 | "Liu" : {'type' : 'displacement', 317 | 'fmax2f0' : 4.0, 318 | 'freq2f0' : 2 * np.pi, 319 | }, 320 | "Triangle" : {'type' : 'velocity', 321 | 'fmax2f0' : 0.0, 322 | 'freq2f0' : 0.0, 323 | }, 324 | "Sawtooth" : {'type' : 'velocity', 325 | 'fmax2f0' : 0.0, 326 | 'freq2f0' : 0.0, 327 | }, 328 | "Ramp" : {'type' : 'displacement', 329 | 'fmax2f0' : 0.0, 330 | 'freq2f0' : 0.0, 331 | }, 332 | "Smoothwave" : {'type' : 'velocity', 333 | 'fmax2f0' : 3.0, 334 | 'freq2f0' : 0.0, 335 | }, 336 | "VerySmoothBump" : {'type' : 'velocity', 337 | 'fmax2f0' : 3.0, 338 | 'freq2f0' : 1.0, 339 | }, 340 | "C6SmoothBump" : {'type' : 'velocity', 341 | 'fmax2f0' : 3.0, 342 | 'freq2f0' : 1.0, 343 | }, 344 | "GaussianWindow" : {'type' : 'velocity', 345 | 'fmax2f0' : 2.5, 346 | 'freq2f0' : 2 * np.pi, 347 | }, 348 | "Dirac" : {'type' : 'velocity', 349 | 'fmax2f0' : 0.0, 350 | 'freq2f0' : 0.0, 351 | }, 352 | } 353 | 354 | # Store the source time function dictionary as an AttribDict(). 355 | STF = AttribDict() 356 | 357 | for key, values in STF_.items(): 358 | STF_item = AttribDict() 359 | STF.setdefault(key, STF_item) 360 | 361 | for key, value in values.items(): 362 | STF_item[key] = value 363 | 364 | 365 | REC_MODE = {'displacement' : '.[x,y,z,e,n,u]' , 366 | 'velocity' : '.[x,y,z,e,n,u]v' , 367 | 'div' : '.div' , 368 | 'curl' : 'curl[x,y,z]' , 369 | 'strains' : '.[x,y,z][x,y,z]' } 370 | -------------------------------------------------------------------------------- /pySW4/plotting/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Plotting routines of pySW4. 4 | """ 5 | from __future__ import absolute_import, print_function, division 6 | 7 | from .waveforms import create_seismogram_plots, plot_traces 8 | from .hillshade import (calc_intensity, shade_and_color, shade_colorbar) 9 | from .image import create_image_plots 10 | 11 | -------------------------------------------------------------------------------- /pySW4/plotting/hillshade.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Plotting routines for shaded-relief and GMT style draping of data. 4 | 5 | .. module:: hillshade 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | :copyright: 11 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 12 | 13 | :license: 14 | This code is distributed under the terms of the 15 | GNU Lesser General Public License, Version 3 16 | (https://www.gnu.org/copyleft/lesser.html) 17 | """ 18 | from __future__ import absolute_import, print_function, division 19 | 20 | import numpy as np 21 | import matplotlib.pyplot as plt 22 | from matplotlib.colors import LightSource, Normalize, rgb_to_hsv, hsv_to_rgb 23 | # from matplotlib.colorbar import ColorbarBase 24 | # from mpl_toolkits.axes_grid1 import make_axes_locatable 25 | 26 | from scipy.ndimage import uniform_filter 27 | 28 | INT_AND_FLOAT = ([int, float] 29 | + np.sctypes.get('int') 30 | + np.sctypes.get('uint') 31 | + np.sctypes.get('float')) 32 | 33 | 34 | def calc_intensity(relief, azimuth=315., altitude=45., 35 | scale=None, smooth=None, normalize=False): 36 | """ 37 | Calculate the illumination intensity of ``relief``. 38 | 39 | Can be used as to create a shaded relief map and GMT style draping 40 | of data. 41 | 42 | It is assumed that the grid origin is at the upper-left corner. 43 | If that is not the case, add 90 to ``azimuth``. 44 | 45 | This function produces similar results to the 46 | :meth:`~matplotlib.colors.LightSource.hillshade` method of 47 | matplotlib but gives extra control in terms of how the result is 48 | normalized. 49 | 50 | Parameters 51 | ---------- 52 | relief : a 2d :class:`~numpy.ndarray` 53 | Topography or other data to calculate intensity from. 54 | 55 | azimuth : float 56 | Direction of light source, degrees from north. 57 | 58 | altitude : float 59 | Height of light source, degrees above the horizon. 60 | 61 | scale : float 62 | Scaling value of the data. 63 | 64 | smooth : float 65 | Number of cells to average before intensity calculation. 66 | 67 | normalize : bool or float 68 | By default the intensity is clipped to the [0,1] range. If set 69 | to ``True``, intensity is normalized to [0,1]. Otherwise, give a 70 | float value to normalize to [0,1] and multiply by the value 71 | before clipping to [0,1]. If ``normalize`` > 1, illumination 72 | becomes brighter and if < 1 illumination becomes darker. 73 | 74 | Returns 75 | ------- 76 | intensity : :class:`~numpy.ndarray` 77 | a 2d array with illumination in the [0,1] range. 78 | Same size as ``relief``. 79 | """ 80 | 81 | relief = relief.copy() 82 | 83 | if scale is not None: 84 | relief = relief * scale 85 | if smooth: 86 | relief = uniform_filter(relief, size=smooth) 87 | 88 | dzdy, dzdx = np.gradient(relief) 89 | 90 | slope = 0.5 * np.pi - np.arctan(np.sqrt(dzdx**2 + dzdy**2)) 91 | 92 | aspect = np.arctan2(dzdx, dzdy) 93 | 94 | altitude = np.radians(altitude) 95 | azimuth = np.radians((azimuth - 90) % 360) 96 | 97 | intensity = (np.sin(altitude) * np.sin(slope) + 98 | np.cos(altitude) * np.cos(slope) * 99 | np.cos(-azimuth - 0.5 * np.pi - aspect)) 100 | 101 | if normalize: 102 | intensity = (normalize * 103 | (intensity - intensity.min()) / intensity.ptp()) 104 | 105 | return intensity.clip(0, 1) 106 | 107 | 108 | def shade_and_color(relief, data=None, az=315, alt=45, vmax='max', vmin='min', 109 | cmap='gist_earth', smooth=None, scale=None, 110 | blend_mode='multiply', contrast=1, brightness=1, 111 | method='matplotlib', normalize=False, 112 | return_shading_dict=False, **kwargs): 113 | """ 114 | Shade (relief) and color (relief or data). 115 | 116 | This is done using the :meth:`~matplotlib.colors.LightSource.shade` 117 | method of the :class:`~matplotlib.colors.LightSource` class. 118 | 119 | Parameters 120 | ---------- 121 | relief : 2d :class:`~numpy.ndarray` 122 | Contains elevation (usually) data. Used for calculating 123 | intensities. 124 | 125 | data : None or 2d :class:`~numpy.ndarray` 126 | Data to color (drape) over the intensity grid. If ``None``, 127 | relief is colored over the intensity grid 128 | 129 | az : int or float 130 | The azimuth (0-360, degrees clockwise from North) of the light 131 | source. Defaults to 315 degrees (from the northwest). 132 | 133 | alt : int or float 134 | The altitude (0-90, degrees up from horizontal) of the light 135 | source. Defaults to 45 degrees from horizontal. 136 | 137 | vmax, vmin : str or float 138 | Used to clip the coloring of the data at the set value. 139 | Default is 'max' and 'min' which used the extent of the data. 140 | If ``float``s, the colorscale saturates at the given values. 141 | Finally, if a string is passed (other than 'max' or 'min'), it 142 | is casted to float and used as an ``rms`` multiplier. For 143 | instance, if ``vmax='3'``, clipping is done at 3.0\*rms of the 144 | data. 145 | 146 | To force symmetric coloring around 0 set `vmin` to ``None`` or 147 | ``False``. This will cause `vmin` to equal -1 * `vmax`. 148 | 149 | cmap : str or :class:`~matplotlib.colors.Colormap` instance 150 | String of the name of the colormap, i.e. 'Greys', or a 151 | :class:`~matplotlib.colors.Colormap` instance. 152 | 153 | smooth : float 154 | Smooth the relief before calculating the intensity grid. This 155 | reduces noise. The overlaid relief is untouched. 156 | 157 | scale : float 158 | Scaling value of the data. Higher definition is achieved by 159 | scaling the elevation prior to the intensity calculation. 160 | This can be used either to correct for differences in units 161 | between the x, y coordinate system and the elevation coordinate 162 | system (e.g. decimal degrees vs meters). 163 | 164 | blend_mode : str or callable 165 | The type of blending used to combine the colormapped data values 166 | with the illumination intensity. For backwards compatibility, 167 | this defaults to 'hsv'. Note that for most topographic 168 | surfaces, 'overlay' or 'multiply' appear more visually 169 | realistic. If a user-defined function is supplied, it is 170 | expected to combine an MxNx3 RGB array of floats (ranging 0 to 171 | 1) with an MxNx1 hillshade array (also 0 to 1). (Call signature 172 | func(rgb, illum, \*\*kwargs)) 173 | 174 | Options are: 175 | **'hsv', 'overlay', 'soft'** - are achieved using the 176 | :meth:`~matplotlib.colors.LightSource.shade_rgb` method of the 177 | :class:`~matplotlib.colors.LightSource` class. 178 | 179 | **'multiply', 'hard', 'screen', 'pegtop'** - are achieved by 180 | image manipulation in RGB space. See :func:`~.multiply`, 181 | :func:`~.hard`, :func:`~.screen`, :func:`~.pegtop`. 182 | 183 | contrast : float 184 | Increases or decreases the contrast of the resulting image. 185 | If > 1 intermediate values move closer to full illumination or 186 | shadow (and clipping any values that move beyond 0 or 1). 187 | 188 | brightness : float 189 | Increases or decreases the brightness of the resulting image. 190 | Ignored for 'hsv', 'overlay', 'soft'. 191 | 192 | method : {'matplotlib', 'calc_intensity'} 193 | By default, matplotlib's 194 | :meth:`~matplotlib.colors.LightSource.hillshade` is used to 195 | calculate the illumination intensity of the relief. 196 | For better control, :func:`~.calc_intensity` can be used 197 | instead. 198 | 199 | normalize : bool or float 200 | By default the intensity is clipped to the [0,1] range. If set 201 | to ``True``, intensity is normalized to [0,1]. Otherwise, give a 202 | float value to normalize to [0,1] and then divide by the value. 203 | 204 | This is ignored if ``method`` is 'matplotlib'. 205 | 206 | return_shading_dict : bool 207 | False by default. If ``True``, a dictionary is returned that can 208 | be passed to :func:`~matplotlib.pyplot.imshow` and 209 | :func:`~.shade_colorbar` to preserve consistency. 210 | 211 | .. note:: It is assumed that relief and data have the same extent, 212 | origin and shape. Any resizing and reshaping must be done prior 213 | to this. 214 | 215 | Other parameters 216 | ---------------- 217 | kwargs : ``blend_mode`` options. 218 | """ 219 | if blend_mode in BLEND_MODES or callable(blend_mode): 220 | blend_func = BLEND_MODES[blend_mode] 221 | else: 222 | raise ValueError('"blend_mode" must be callable or one of {}' 223 | .format(BLEND_MODES.keys)) 224 | 225 | relief = relief.copy() 226 | data = data if data is not None else relief 227 | 228 | if scale is not None: 229 | relief = relief * scale 230 | if smooth: 231 | relief = uniform_filter(relief, size=smooth) 232 | 233 | # data 234 | if vmax is 'max': 235 | clip = np.nanmax(data) 236 | elif type(vmax) in INT_AND_FLOAT: 237 | clip = vmax 238 | else: 239 | clip = float(vmax) * np.nanstd(data) 240 | 241 | if vmin is 'min': 242 | vmin = np.nanmin(data) 243 | elif type(vmin) in INT_AND_FLOAT: 244 | pass 245 | elif vmin in [None, False]: 246 | vmin = -clip 247 | 248 | vmax = clip 249 | 250 | # matplotlib shade_rgb using hillshade intensity calculation 251 | if method == 'matplotlib': 252 | ls = LightSource(azdeg=az, altdeg=alt) 253 | 254 | # color and drape the data on top of the shaded relief 255 | rgb = ls.shade_rgb( 256 | data2rgb(data, cmap, vmin, vmax), relief, 257 | blend_mode=blend_func, fraction=contrast, **kwargs) 258 | 259 | # use calc_intensity with better control 260 | else: 261 | rgb = data2rgb(data, cmap, vmin, vmax) 262 | illumination = calc_intensity(relief, az, alt, normalize=normalize) 263 | 264 | illumination = illumination[..., np.newaxis] 265 | 266 | if blend_mode in BLEND_MODES: 267 | blend_func = BLEND_MODES[blend_mode] 268 | elif callable(blend_mode): 269 | blend_func = blend_mode 270 | else: 271 | raise ValueError('"blend_mode" must be callable or one of {}' 272 | .format(BLEND_MODES.keys)) 273 | 274 | rgb = blend_func(rgb, illumination) 275 | 276 | if brightness != 1: 277 | rgb = adjust_brightness(rgb, brightness) 278 | 279 | if return_shading_dict: 280 | return rgb, dict(cmap=cmap, vmin=vmin, vmax=vmax, 281 | blend_mode=blend_func, brightness=brightness) 282 | return rgb 283 | 284 | 285 | def data2rgb(data, cmap='Greys_r', vmin=None, vmax=None, norm=None, 286 | bytes=False): 287 | if isinstance(cmap, str): 288 | cmap = plt.get_cmap(cmap) 289 | 290 | norm = norm or Normalize(vmin, vmax) 291 | return cmap(norm(data), bytes=bytes)[:, :, :3] 292 | 293 | 294 | def adjust_brightness(rgb, brightness): 295 | hsv = rgb_to_hsv(rgb[:, :, :3]) 296 | hsv[:, :, -1] *= brightness 297 | return hsv_to_rgb(hsv) 298 | 299 | 300 | def multiply(rgb, illum): 301 | rgb = rgb[:, :, :3] 302 | illum = illum[:, :, :3] 303 | return np.clip(rgb * illum, 0.0, 1.0) 304 | 305 | 306 | def screen(rgb, illum): 307 | rgb = rgb[:, :, :3] 308 | illum = illum[:, :, :3] 309 | return np.clip(1 - (1 - rgb) * (1 - illum), 0.0, 1.0) 310 | 311 | 312 | def hard(rgb, illum): 313 | rgb = rgb[:, :, :3] 314 | illum = illum[:, :, :3] 315 | image = np.where(illum < 0.5, 316 | 2 * rgb * illum, 317 | 1 - 2 * (1 - illum) * (1 - rgb)) 318 | return np.clip(image, 0.0, 1.0) 319 | 320 | 321 | def pegtop(rgb, illum): 322 | rgb = rgb[:, :, :3] 323 | illum = illum[:, :, :3] 324 | image = (1 - 2 * rgb) * illum**2 + 2 * rgb * illum 325 | return np.clip(image, 0.0, 1.0) 326 | 327 | 328 | def shade_colorbar(cb, max_illum=1, min_illum=0.1, n=3, 329 | **kwargs): 330 | """ 331 | Shade a colorbar to add illumination effects similar to GMT psscale. 332 | 333 | Parameters 334 | ---------- 335 | cb : :class:`~matplotlib.colorbar.Colorbar` 336 | A colorbar instance to add illumination effects to. 337 | 338 | max_illum, min_illum : float 339 | The maximum (light) and minimum(dark) illumination values. 340 | 341 | n : int 342 | Number of illumination levels. A higher value generates more 343 | levels of illumination between ``min_illum`` and ``max_illum`` 344 | but the result is linearly interpolated so 3 is a good enough. 345 | Larger values are slower. 346 | 347 | Other parameters 348 | ---------------- 349 | kwargs : dict 350 | A dictionary returned by :func:`~.shade_and_color`. Expected 351 | keys are 'cmap', 'vmin', 'vmax', 'blend_mode', and 'brightness'. 352 | Other keywards are passed to blend_mode. 353 | """ 354 | x = np.linspace(0, 1, 256) 355 | y = np.linspace(min_illum, max_illum, n) 356 | C1, C0 = np.meshgrid(x, y) 357 | 358 | xmin = ymin = cb.vmin 359 | xmax = ymax = cb.vmax 360 | 361 | if cb.orientation == 'vertical': 362 | C1 = C1.T[::-1] 363 | C0 = C0.T[:, ::-1] 364 | 365 | illumination = C0[::-1]**0.5 366 | 367 | cmap = cb.cmap 368 | rgb = data2rgb(C1, cmap) 369 | 370 | illumination = illumination[..., np.newaxis] 371 | 372 | blend_mode = kwargs['blend_mode'] 373 | 374 | if blend_mode in BLEND_MODES: 375 | blend_func = BLEND_MODES[blend_mode] 376 | elif callable(blend_mode): 377 | blend_func = blend_mode 378 | else: 379 | raise ValueError('"blend_mode" must be callable or one of {}' 380 | .format(BLEND_MODES.keys)) 381 | 382 | rgb = blend_func(rgb, illumination) 383 | if kwargs['brightness'] != 1: 384 | rgb = adjust_brightness(rgb, kwargs['brightness']) 385 | cb.ax.imshow(rgb, interpolation='bilinear', aspect=cb.ax.get_aspect(), 386 | extent=(xmin, xmax, ymin, ymax), zorder=1) 387 | 388 | 389 | ls = LightSource() 390 | 391 | # Blend the hillshade and rgb data using the specified mode 392 | BLEND_MODES = { 393 | 'hsv' : ls.blend_hsv, 394 | 'soft' : ls.blend_soft_light, 395 | 'overlay' : ls.blend_overlay, 396 | 'multiply' : multiply, 397 | 'hard' : hard, 398 | 'screen' : screen, 399 | 'pegtop' : pegtop, 400 | } 401 | -------------------------------------------------------------------------------- /pySW4/plotting/image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Plotting routines for SW4 images of Maps or Cross-Sections. 4 | 5 | .. module:: image 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (€€€€) 9 | 10 | Omry Volk (omryv@post.bgu.ac.il) 11 | 12 | Tobias Megies (megies@geophysik.uni-muenchen.de) 13 | 14 | :copyright: 15 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 16 | 17 | Omry Volk (omryv@post.bgu.ac.il) 18 | 19 | Tobias Megies (megies@geophysik.uni-muenchen.de) 20 | 21 | :license: 22 | This code is distributed under the terms of the 23 | GNU Lesser General Public License, Version 3 24 | (https://www.gnu.org/copyleft/lesser.html) 25 | """ 26 | from __future__ import absolute_import, print_function, division 27 | 28 | from glob import glob 29 | import os 30 | import re 31 | 32 | import subprocess 33 | from warnings import warn 34 | from io import BytesIO 35 | 36 | import numpy as np 37 | import matplotlib.pyplot as plt 38 | try: 39 | from obspy.imaging.cm import obspy_divergent as cmap_divergent 40 | from obspy.imaging.cm import obspy_divergent_r as cmap_divergent_r 41 | from obspy.imaging.cm import obspy_sequential as cmap_sequential 42 | from obspy.imaging.cm import obspy_sequential_r as cmap_sequential_r 43 | except ImportError: 44 | cmap_divergent = None 45 | cmap_divergent_r = None 46 | cmap_sequential = None 47 | cmap_sequential_r = None 48 | 49 | from ..postp import read_image 50 | from ..sw4_metadata import _parse_input_file_and_folder 51 | from ..headers import STF 52 | 53 | 54 | def image_files_to_movie( 55 | input_filenames, output_filename, input_file=None, 56 | stf=None, patch_number=0, 57 | frames_per_second=5, overwrite=False, global_colorlimits=True, 58 | debug=False, **plot_kwargs): 59 | """ 60 | Convert SW4 images to an mp4 movie using command line ffmpeg. 61 | 62 | Parameters 63 | ---------- 64 | input_filenames : str or list 65 | Wildcarded filename pattern or list of individual filenames. 66 | 67 | output_filename : str 68 | Output movie filename ('.mp4' extension will be appended if not 69 | already present). 70 | """ 71 | if not output_filename.endswith(".mp4"): 72 | output_filename += ".mp4" 73 | if os.path.exists(output_filename): 74 | if overwrite: 75 | os.remove(output_filename) 76 | if os.path.exists(output_filename): 77 | msg = ("Output path '{}' exists.").format(output_filename) 78 | raise IOError(msg) 79 | 80 | if isinstance(input_filenames, str): 81 | files = sorted(glob.glob(input_filenames)) 82 | else: 83 | files = input_filenames 84 | 85 | # parse all files to determine global value range extrema 86 | # before plotting 87 | if global_colorlimits: 88 | global_min = np.inf 89 | global_max = -np.inf 90 | for file_ in files: 91 | try: 92 | image = read_image( 93 | file_, input_file=input_file, 94 | stf=stf) 95 | except Exception as e: 96 | warn( 97 | 'Unable to read {}: {}. Skipping this file...'.format( 98 | file_, e 99 | ) 100 | ) 101 | patch = image.patches[patch_number] 102 | global_min = min(global_min, patch._min) 103 | global_max = max(global_max, patch._max) 104 | if image.is_divergent: 105 | abs_max = max(abs(global_min), abs(global_max)) 106 | plot_kwargs["vmin"] = -abs_max 107 | plot_kwargs["vmax"] = abs_max 108 | else: 109 | plot_kwargs["vmin"] = global_min 110 | plot_kwargs["vmax"] = global_max 111 | if global_min == np.inf and global_max == -np.inf: 112 | msg = ("Invalid global data limits for files '{}'").format( 113 | input_filenames) 114 | raise ValueError(msg) 115 | 116 | cmdstring = ( 117 | 'ffmpeg', '-loglevel', 'fatal', '-r', '%d' % frames_per_second, 118 | '-f', 'image2pipe', '-vcodec', 'png', '-i', 'pipe:', 119 | '-vcodec', 'libx264', '-pass', '1', '-vb', '6M', '-pix_fmt', 'yuv420p', 120 | output_filename) 121 | 122 | bytes_io = BytesIO() 123 | backend = plt.get_backend() 124 | try: 125 | plt.switch_backend('AGG') 126 | # plot all images and pipe the pngs to ffmpeg 127 | for file_ in files: 128 | image = read_image( 129 | file_, stf=stf, 130 | input_file=input_file) 131 | patch = image.patches[patch_number] 132 | fig, _, _ = patch.plot(**plot_kwargs) 133 | fig.tight_layout() 134 | fig.savefig(bytes_io, format='png') 135 | plt.close(fig) 136 | 137 | bytes_io.seek(0) 138 | png_data = bytes_io.read() 139 | bytes_io.close() 140 | sub = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, 141 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 142 | 143 | stdout, stderr = sub.communicate(png_data) 144 | if debug: 145 | print("###### ffmpeg stdout") 146 | print(stdout) 147 | print("###### ffmpeg stderr") 148 | print(stderr) 149 | sub.wait() 150 | for ffmpeg_tmp_file in ("ffmpeg2pass-0.log", 151 | "ffmpeg2pass-0.log.mbtree"): 152 | if os.path.exists(ffmpeg_tmp_file): 153 | os.remove(ffmpeg_tmp_file) 154 | finally: 155 | plt.switch_backend(backend) 156 | 157 | 158 | def create_image_plots( 159 | input_file, folder=None, stf=None, 160 | frames_per_second=5, cmap=None, movies=True): 161 | """ 162 | Create all image plots/movies for a SW4 run. 163 | 164 | Currently always only uses first patch in each SW4 image file. 165 | If the path/filename of the SW4 input file is provided, additional 166 | information is included in the plots (e.g. receiver/source location, 167 | automatic determination of source time function type, etc...) 168 | 169 | Parameters 170 | ---------- 171 | input_file : str 172 | Filename (potentially with absolute/relative path) of SW4 input 173 | file used to control the simulation. Use ``None`` to work on 174 | folder with SW4 output without using metadata from input file. 175 | 176 | folder : str 177 | Folder with SW4 output files or ``None`` if output folder 178 | location can be used from input file. Only needed when no 179 | input file is specified or if output folder was moved to a 180 | different location after the simulation. 181 | 182 | stf : str 183 | ``'displacement'`` or ``'velocity'``. 184 | 185 | frames_per_second : int or float 186 | Image frames to show per second in output videos. 187 | 188 | cmap : str or :class:`~matplotlib.colors.Colormap` 189 | Matplotlib colormap or colormap string understood by matplotlib. 190 | 191 | movies : bool 192 | Whether to produce movies from image files present at different 193 | cycles of the simulation. Needs `ffmpeg `_ 194 | to be installed and callable on command line. 195 | """ 196 | input_, folder = _parse_input_file_and_folder(input_file, folder) 197 | 198 | if stf is None and input_ is None: 199 | msg = ("No input file specified (option `input_file`) " 200 | "and source time function type not specified explicitely " 201 | "(option `stf`).") 202 | raise ValueError(msg) 203 | 204 | stf = stf or STF[input_.source[0].type].type 205 | 206 | if not os.path.isdir(folder): 207 | msg = "Not a folder: '{}'".format(folder) 208 | raise ValueError(msg) 209 | 210 | all_files = glob(os.path.join(folder, "*.sw4img")) 211 | if not all_files: 212 | msg = "No *.sw4img files in folder '{}'".format(folder) 213 | return Exception(msg) 214 | 215 | # build individual lists, one for each specific property 216 | grouped_files = {} 217 | for file_ in all_files: 218 | # e.g. shakemap.cycle=000.z=0.hmag.sw4img 219 | prefix, _, coordinate, type_ = \ 220 | os.path.basename(file_).rsplit(".", 4)[:-1] 221 | grouped_files.setdefault((prefix, coordinate, type_), []).append(file_) 222 | for files in grouped_files.values(): 223 | # create individual plots as .png 224 | for file_ in files: 225 | try: 226 | image = read_image( 227 | file_, stf=stf, 228 | input_file=input_file) 229 | except Exception as e: 230 | warn( 231 | 'Unable to read {}: {}. Skipping this file...'.format( 232 | file_, e 233 | ) 234 | ) 235 | outfile = file_.rsplit(".", 1)[0] + ".png" 236 | fig, _, _ = image.plot(cmap=cmap) 237 | fig.savefig(outfile) 238 | plt.close(fig) 239 | # if several individual files in the group, also create a movie as .mp4 240 | if movies: 241 | if len(files) > 2: 242 | files = sorted(files) 243 | movie_filename = re.sub( 244 | r'([^.]*)\.cycle=[0-9]*\.(.*?)\.sw4img', 245 | r'\1.cycle=XXX.\2.mp4', files[0]) 246 | try: 247 | image_files_to_movie( 248 | files, movie_filename, 249 | frames_per_second=frames_per_second, 250 | stf=stf, 251 | overwrite=True, cmap=cmap, input_file=input_file) 252 | except Exception as e: 253 | msg = ("Failed to create a movie: {}").format(str(e)) 254 | warn(msg) 255 | -------------------------------------------------------------------------------- /pySW4/plotting/png2mp4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Convert a sequencial set of .png images to a .mp4 movies. 4 | 5 | .. module:: png2mp4 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | :copyright: 11 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 12 | 13 | :license: 14 | This code is distributed under the terms of the 15 | GNU Lesser General Public License, Version 3 16 | (https://www.gnu.org/copyleft/lesser.html) 17 | """ 18 | from __future__ import absolute_import, print_function, division 19 | 20 | import os 21 | import sys 22 | import time 23 | import subprocess as sub 24 | from warnings import warn 25 | 26 | 27 | def png2mp4(outfile, inpath='./', crf=23, pts=1, fps=30, verbose=True): 28 | """ 29 | Python module for creating mp4 annimations from a set of 30 | sequential png images. 31 | 32 | Parameters 33 | ---------- 34 | 35 | outfile : str 36 | Name of the final .mp4 file. 37 | 38 | inpath : str 39 | Path to where the sequential .png images are. Default is `'./'`. 40 | 41 | crf : int 42 | Constant Rate Factor, ranges from 0 to 51: A lower value is a 43 | higher quality and vise versa. The range is exponential, so 44 | increasing the CRF value +6 is roughly half the bitrate while -6 45 | is roughly twice the bitrate. General usage is to choose the 46 | highest CRF value that still provides an acceptable quality. If 47 | the output looks good, then try a higher value and if it looks 48 | bad then choose a lower value. A subjectively sane range is 18 49 | to 28. Default is 23. 50 | 51 | pts : int 52 | Presentation TimeStamp, pts < 1 to speedup the video, pts > 1 to 53 | slow down the video. For example, 0.5 will double the speed and 54 | cut the video duration in half whereas 2 will slow the video 55 | down and double its duration. Default is 1. 56 | 57 | fps : int 58 | Frames Per Second of the output video. If you speed a video up 59 | frames get dropped as ffmpeg is trying to fit more frames in 60 | less time. Try increasing the frame rate by the same factor used 61 | for pts. If you slow a video down, increasing the frame rate 62 | results in smoother video for some reason. Default is 30. 63 | 64 | verbose : bool 65 | Print information about the process if True. Set to False to 66 | suppress any output. 67 | """ 68 | 69 | inpath = os.path.join(inpath, '*.png') 70 | if os.path.splitext(outfile)[-1] not in ['.mp4', '.MP4']: 71 | outfile += '.mp4' 72 | 73 | if verbose: 74 | print('*** converting sequencial png files to mp4...\n') 75 | sys.stdout.flush() 76 | 77 | t = time.time() 78 | command = ("ffmpeg -y -pattern_type glob -i {} " 79 | "-vcodec libx264 -crf {} -pass 1 -vb 6M " 80 | "-pix_fmt yuv420p " 81 | "-vf scale=trunc(iw/2)*2:trunc(ih/2)*2,setpts={}*PTS " 82 | "-r {} -an {}").format(inpath, crf, pts, fps, outfile) 83 | 84 | command = command.split() 85 | 86 | if verbose: 87 | print('***\ncalling {} with the following arguments:\n' 88 | .format(command[0])) 89 | for item in command[1:]: 90 | print(item, end="") 91 | print('\n***\n') 92 | sys.stdout.flush() 93 | 94 | time.sleep(1) 95 | 96 | p = sub.Popen(command, 97 | stdin=sub.PIPE, 98 | stdout=sub.PIPE, 99 | stderr=sub.PIPE) 100 | p.wait() 101 | 102 | if verbose: 103 | print('\n******\nconvertion took {} seconds'.format(time.time() - t)) 104 | -------------------------------------------------------------------------------- /pySW4/plotting/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Python module with general utilities for making plotting easier. 4 | 5 | .. module:: utils 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | :copyright: 11 | Shahar Shani-Kadmiel 12 | 13 | :license: 14 | This code is distributed under the terms of the 15 | GNU Lesser General Public License, Version 3 16 | (https://www.gnu.org/copyleft/lesser.html) 17 | """ 18 | from __future__ import absolute_import, print_function, division 19 | 20 | from matplotlib import rc 21 | import numpy as np 22 | 23 | 24 | def pretty_ticks(min, max, number_of_ticks=2, show_zero=False): 25 | """ 26 | Function for controlling tickmarks. 27 | 28 | Parameters 29 | ---------- 30 | min : int or float 31 | Minimum value to tick on the axis. 32 | 33 | max : int or float 34 | Maximum value to tick on the axis. 35 | 36 | number_of_ticks : int 37 | The number of tickmarks to plot. Must be > 2. 38 | 39 | show_zero : bool 40 | If set to ``True`` the zero tickmark will be plotted. 41 | 42 | Returns 43 | ------- 44 | :class:`~numpy.ndarray` 45 | A 1d array with tickmark values. 46 | """ 47 | range = max - min 48 | exponent = int(np.abs(np.round(np.log10(range))) + 1) 49 | magnitude = 10**exponent 50 | min = round(min, exponent) 51 | max = round(max, exponent) 52 | ticks, step = np.linspace(min, max, number_of_ticks, retstep=True) 53 | if show_zero and 0 not in ticks: 54 | positive_ticks = ticks[ticks >= 0] 55 | negative_ticks = ticks[ticks < 0] 56 | positive_ticks = np.insert(positive_ticks,0,0.0) 57 | ticks = np.hstack((negative_ticks, positive_ticks)) 58 | return ticks 59 | 60 | 61 | def set_matplotlib_rc_params(): 62 | """ 63 | Set matplotlib rcparams for plotting 64 | """ 65 | font = {'family' : 'sans-serif', 66 | 'sans-serif' : 'Helvetica', 67 | 'style' : 'normal', 68 | 'variant' : 'normal', 69 | 'weight' : 'medium', 70 | 'stretch' : 'normal', 71 | 'size' : 12.0} 72 | rc('font', **font) 73 | 74 | legend = {'fontsize' : 10.0} 75 | rc('legend', **legend) 76 | 77 | axes = {'titlesize' : 14.0, 78 | 'labelsize' : 12.0} 79 | rc('axes', **axes) 80 | rc('pdf', fonttype=42) 81 | 82 | ticks = {'direction' : 'out', 83 | 'labelsize' : 12.0, 84 | 'major.pad' : 4, 85 | 'major.size' : 5, 86 | 'major.width' : 1.0, 87 | 'minor.pad' : 4, 88 | 'minor.size' : 2.5, 89 | 'minor.width' : 0.75} 90 | rc('xtick', **ticks) 91 | rc('ytick', **ticks) 92 | -------------------------------------------------------------------------------- /pySW4/plotting/waveforms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Plotting routines for observed and synthetic waveforms. 4 | 5 | .. module:: waveforms 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | Omry Volk (omryv@post.bgu.ac.il) 11 | 12 | Tobias Megies (megies@geophysik.uni-muenchen.de) 13 | 14 | :copyright: 15 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 16 | 17 | Omry Volk (omryv@post.bgu.ac.il) 18 | 19 | Tobias Megies (megies@geophysik.uni-muenchen.de) 20 | 21 | :license: 22 | This code is distributed under the terms of the 23 | GNU Lesser General Public License, Version 3 24 | (https://www.gnu.org/copyleft/lesser.html) 25 | 26 | 27 | .. rubric:: SW4 uses a right handed coordinate system 28 | 29 | 30 | :: 31 | 32 | X 33 | ⋰ 34 | ⋰ 35 | o------->Y 36 | | 37 | | 38 | V 39 | Z 40 | 41 | If the azimuth of the SW4 grid is ``0`` this translates to: 42 | 43 | - X == Northing 44 | - Y == Easting 45 | - Z == Vertical (inverted!) 46 | """ 47 | from __future__ import absolute_import, print_function, division 48 | 49 | import glob 50 | import os 51 | import warnings 52 | 53 | import matplotlib.pyplot as plt 54 | from matplotlib.dates import date2num 55 | from matplotlib.gridspec import GridSpec 56 | import numpy as np 57 | import obspy 58 | from ..sw4_metadata import _parse_input_file_and_folder 59 | from ..headers import STF 60 | from ..utils import fourier_spectrum 61 | 62 | MODE = {'displacement' : 'displacement, m' , 63 | 'velocity' : 'velocity, m/s' , 64 | 'div' : 'div' , 65 | 'curl' : 'curl' , 66 | 'strains' : 'strain' } 67 | 68 | 69 | def plot_traces(traces, mode='', yscale='auto', hspace=0.2, wspace=0.05, 70 | figsize=None, fig=None, **kwargs): 71 | """ 72 | Plot all traces and their Fourier specra side-by-side. 73 | 74 | Parameters 75 | ---------- 76 | traces : :class:`~obspy.Stream` 77 | Traces to be plotted in an :class:`~obspy.Stream` object. 78 | 79 | mode : {'displacement', 'velocity', 'div', 'curl', 'strains'} 80 | Mode describes the type of data in traces. 81 | 82 | Optionaly, an alternative string can be given that will be used 83 | as the y-label of the time-histories. 84 | 85 | yscale : {'auto', 'all', 'normalize'} 86 | Set the scale of the vertical y-axis: 87 | 88 | - ``auto`` - Vertical scale of each axes is automatically set to 89 | the -\|max\| and \|max\| of each trace. 90 | 91 | - ``all`` - Vertical scale of all axes is set to the same limits 92 | which are the -\|max\| and \|max\| of all traces. 93 | 94 | - ``normalize`` - Each trace is normalized and plotted 95 | (``ylim=(-1, 1)``). 96 | 97 | 98 | hspace : float 99 | The hight space between axes. See 100 | :class:`~matplotlib.gridspec.GridSpec` documentation. 101 | 102 | wspace : float 103 | The width space between axes. See 104 | :class:`~matplotlib.gridspec.GridSpec` documentation. 105 | 106 | figsize : 2-tuple 107 | Size of the :class:`~matplotlib.figure.Figure`. 108 | 109 | fig : :class:`~matplotlib.figure.Figure` 110 | A :class:`~matplotlib.figure.Figure` instance. 111 | 112 | Other Parameters 113 | ---------------- 114 | kwargs : :func:`~matplotlib.pyplot.plot` propeties. 115 | """ 116 | count = traces.count() 117 | 118 | if not fig: 119 | set_title = True 120 | if not figsize: 121 | figsize = (6, 2 * int(count)) 122 | 123 | gs = GridSpec(count, 2, width_ratios=[2, 1], 124 | hspace=hspace, wspace=wspace) 125 | fig = plt.figure(figsize=figsize) 126 | ax = [fig.add_subplot(item) for item in gs] 127 | else: 128 | ax = fig.get_axes() 129 | set_title = False 130 | 131 | _min, _max = -max(np.abs(traces.max())), max(np.abs(traces.max())) 132 | for i, tr in enumerate(traces.copy()): 133 | # plot seismogram 134 | axi = ax[i * 2] 135 | if yscale is 'auto': 136 | axi.set_ylim(-np.abs(tr.max()), 137 | np.abs(tr.max())) 138 | elif yscale is 'all': 139 | axi.set_ylim(_min, _max) 140 | elif yscale is 'normalize': 141 | tr.normalize() 142 | axi.set_ylim(-1, 1) 143 | axi.yaxis.tick_left() 144 | if set_title: 145 | location = '' 146 | for k, v in zip(tr.stats.coordinates.keys(), 147 | tr.stats.coordinates.values()): 148 | location += '{}\n'.format(k + '=' + str(v)) 149 | axi.text(0.99, 0.97, 150 | '{}:\n{}'.format(tr.id.split('..')[0], location), 151 | color='r', 152 | transform=axi.transAxes, ha='right', va='top', 153 | fontsize=8) 154 | axi.text(0.01, 0.03, 155 | tr.stats.starttime, color='r', 156 | transform=axi.transAxes, ha='left', va='bottom', 157 | fontsize=8) 158 | 159 | try: 160 | axi.set_ylabel(MODE[mode]) 161 | except KeyError: 162 | axi.set_ylabel(mode) 163 | 164 | axi.plot(tr.times(), tr.data, **kwargs) 165 | axi.set_xlim(tr.times()[0], tr.times()[-1]) 166 | 167 | # plot spectrum 168 | axi = ax[i * 2 + 1] 169 | freq, amp = fourier_spectrum(tr) 170 | axi.loglog(freq, amp, **kwargs) 171 | axi.yaxis.tick_right() 172 | axi.grid(True, ls='dashed') 173 | axi.set_xlim(freq[0], freq[-1]) 174 | 175 | for axi in ax[:-2]: 176 | axi.set_xticklabels([]) 177 | 178 | axi = ax[-2] 179 | axi.set_xlabel('time, s') 180 | axi = ax[-1] 181 | axi.set_xlabel('frequency, Hz') 182 | 183 | return fig, ax 184 | 185 | 186 | def create_seismogram_plots( 187 | input_file, folder=None, stream_observed=None, inventory=None, 188 | water_level=None, pre_filt=None, filter_kwargs=None, 189 | channel_map=None, used_stations=None, synthetic_starttime=None, 190 | synthetic_data_glob='*.?v', t0_correction_fraction=0.0, 191 | synthetic_scaling=False, verbose=False): 192 | """ 193 | Create all waveform plots, comparing synthetic and observed data. 194 | 195 | Ideally works on a SW4 input file, or explicitely on an folder with 196 | SW4 output files. Assumes output in SAC format. Observed/real data 197 | and station metadata can be specified, along with deconvolution 198 | parameters to get to physical units. 199 | 200 | Parameters 201 | ---------- 202 | input_file : str 203 | Filename (potentially with absolute/relative path) of SW4 input 204 | file used to control the simulation. Use ``None`` to work on 205 | folder with SW4 output without using metadata from input file. 206 | 207 | folder : str 208 | Folder with SW4 output files or ``None`` if output folder 209 | location can be used from input file. Only needed when no input 210 | file is specified or if output folder was moved to a different 211 | location after the simulation. 212 | 213 | stream_observed : :class:`obspy.core.stream.Stream` 214 | Observed/real data to compare with synthetics. 215 | 216 | inventory : :class:`obspy.core.inventory.inventory.Inventory` 217 | Station metadata for observed/real data. 218 | 219 | water_level : float 220 | Water level for instrument response removal (see 221 | :meth:`obspy.core.trace.remove_response`). 222 | 223 | pre_filt : 4-tuple of float 224 | Frequency domain pre-filtering in response removal (see 225 | :meth:`obspy.core.trace.Trace.remove_response`). 226 | 227 | filter_kwargs : dict 228 | Filter parameters for filtering applied to observed data after 229 | response removal before comparison to synthetic data. ``kwargs`` 230 | are passed on to :meth:`obspy.core.stream.Stream.filter`. 231 | 232 | channel_map : dict 233 | Mapping dictionary to match synthetic channel to component in 234 | observed data. 235 | 236 | used_stations : list 237 | Station codes to consider in plot output. Use all stations if 238 | left ``None``. 239 | 240 | synthetic_starttime : :class:`obspy.core.utcdatetime.UTCDateTime` 241 | Start time of synthetic data, only needed if no input file is 242 | specified or if input file did not set the correct origin time 243 | of the event. 244 | 245 | synthetic_data_glob : str 246 | Glob pattern to lookup synthetic data. Use e.g. 247 | '\*.[xyz]' or '\*.?' for synthetic data saved as "displacement" 248 | (the solution of the forward solver), or '\*.?v' for synthetic 249 | data saved as "velocity" (the differentiated solution of the 250 | forward solver). 251 | 252 | t0_correction_fraction : float 253 | Fraction of t0 used in SW4 simulation 254 | (offset of source time function to prevent solver artifacts) to 255 | account for (i.e. shift synthetics left to earlier absolute 256 | time). '0.0' means no correction of synthetics time is done, 257 | '1.0' means that synthetic trace is shifted left in time by 258 | ``t0`` of SW4 run. 259 | 260 | synthetic_scaling : bool or float 261 | Scaling to apply to synthetic seismograms. If 262 | ``False``, no scaling is applied. If a float is provided, all 263 | synthetics' will be scaled with the given factor (e.g. using 264 | ``2.0`` will scale up synthetics by a factor of 2). 265 | """ 266 | input_, folder = _parse_input_file_and_folder(input_file, folder) 267 | 268 | stf_type = STF[input_.source[0].type].type 269 | if stf_type == 'displacement': 270 | evalresp_output = "DISP" 271 | unit_label = "m" 272 | elif stf_type == 'velocity': 273 | evalresp_output = "VEL" 274 | unit_label = "m/s" 275 | else: 276 | raise NotImplementedError() 277 | 278 | if verbose: 279 | print('Correcting observed seismograms to output "{}" using ' 280 | 'evalresp.'.format(evalresp_output)) 281 | 282 | if channel_map is None: 283 | channel_map = { 284 | "-Vz": "Z", "Vx": "N", "Vy": "E", 285 | "-Z": "Z", "X": "N", "Y": "E"} 286 | 287 | info_text = '' 288 | 289 | files_synth = glob.glob(os.path.join(folder, synthetic_data_glob)) 290 | st_synth = obspy.Stream() 291 | for file_ in files_synth: 292 | st_synth += obspy.read(file_) 293 | if t0_correction_fraction: 294 | if len(config.source) > 1: 295 | msg = ('t0_correction_fraction is not implemented for SW4 run ' 296 | 'with multiple sources.') 297 | raise NotImplementedError(msg) 298 | t0 = config.source[0].t0 299 | t0_correction = t0 * t0_correction_fraction 300 | info_text += ( 301 | ' Synthetics start time corrected by {}*t0 ' 302 | '(-{}s).').format(t0_correction_fraction, t0_correction) 303 | for tr in st_synth: 304 | tr.stats.starttime -= t0_correction 305 | if synthetic_scaling is not False: 306 | info_text += (' Synthetics scaled with a factor of {}.').format( 307 | synthetic_scaling) 308 | for tr in st_synth: 309 | tr.data *= synthetic_scaling 310 | if verbose: 311 | print('Scaling synthetics by {}'.format(synthetic_scaling)) 312 | 313 | st_real = stream_observed or obspy.Stream() 314 | if used_stations is not None: 315 | st_synth.traces = [tr for tr in st_synth 316 | if tr.stats.station in used_stations] 317 | stations = set([tr.stats.station for tr in st_synth]) 318 | st_real.traces = [tr for tr in st_real 319 | if tr.stats.station in stations] 320 | for tr in st_synth: 321 | # SW4 vertical channel is positive in coordinate direction, which 322 | # is depth positive down. So we have to invert it to get the normal 323 | # seismometer vertical up trace. 324 | if tr.stats.channel == "Vz": 325 | tr.stats.channel = "-Vz" 326 | tr.data *= -1 327 | elif tr.stats.channel == "Z": 328 | tr.stats.channel = "-Z" 329 | tr.data *= -1 330 | t_min = min([tr.stats.starttime for tr in st_synth]) 331 | t_max = min([tr.stats.endtime for tr in st_synth]) 332 | st_real.attach_response(inventory) 333 | st_real.remove_response( 334 | output=evalresp_output, water_level=water_level, pre_filt=pre_filt) 335 | if filter_kwargs: 336 | st_real.filter(**filter_kwargs) 337 | st_real.trim(t_min, t_max) 338 | 339 | outfile = os.path.join(folder, "seismograms.png") 340 | _plot_seismograms(st_synth, st_real, channel_map, unit_label, outfile, 341 | info_text=info_text) 342 | for station in stations: 343 | outfile = os.path.join(folder, "seismograms.{}.png".format(station)) 344 | st_synth_ = st_synth.select(station=station) 345 | st_real_ = st_real.select(station=station) 346 | _plot_seismograms( 347 | st_synth_, st_real_, channel_map, unit_label, outfile, 348 | figsize=(10, 8), info_text=info_text) 349 | 350 | 351 | def _plot_seismograms( 352 | st_synth_, st_real_, channel_map, unit_label, outfile, figsize=None, 353 | info_text=None): 354 | """ 355 | Helper function that plots synthetic vs. real data to an image file. 356 | 357 | Parameters 358 | ---------- 359 | st_synth : :class:`obspy.core.stream.Stream` 360 | Synthetic waveform data. 361 | 362 | st_real : :class:`obspy.core.stream.Stream` 363 | Observed waveform data. 364 | 365 | channel_map : dict 366 | Mapping dictionary to match synthetic channel to component in 367 | observed data. 368 | 369 | unit_label : str 370 | Label string for y-axis of waveforms. 371 | 372 | outfile : str 373 | Output filename (absolute or relative path) for image including 374 | suffix (e.g. png). 375 | 376 | figsize : 2-tuple of floats 377 | Matplotlib figure size (inches x/y). 378 | """ 379 | if figsize is None: 380 | figsize = (10, len(st_synth_)) 381 | 382 | fig = plt.figure(figsize=figsize) 383 | st_synth_.plot(fig=fig) 384 | 385 | for ax in fig.axes: 386 | id = ax.texts[0].get_text() 387 | _, sta, _, cha = id.split(".") 388 | real_component = channel_map.get(cha) 389 | if real_component is None: 390 | real_component = cha[-1] 391 | msg = ('Synthetic channel could not be mapped to component code ' 392 | 'of observed data. Using last character of synthetic data ' 393 | '("{}") to match observed data component code. Are data ' 394 | 'already rotated by SW4 through config ' 395 | 'file settings?').format(real_component) 396 | warnings.warn(msg) 397 | # find appropriate synthetic trace 398 | for tr_real in st_real_: 399 | if tr_real.stats.station != sta: 400 | continue 401 | # SW4 synthetics channel codes (for velocity traces) are "V[xyz]" 402 | if tr_real.stats.channel[-1] != real_component: 403 | continue 404 | break 405 | else: 406 | continue 407 | ax.text(0.95, 0.9, tr_real.id, ha="right", va="top", color="r", 408 | transform=ax.transAxes) 409 | t = date2num([(tr_real.stats.starttime + t_).datetime 410 | for t_ in tr_real.times()]) 411 | ax.plot(t, tr_real.data, "r-") 412 | ax.set_ylabel(unit_label) 413 | if info_text: 414 | ax.text(0.95, 0.02, info_text, ha="right", va="bottom", color="b", 415 | transform=ax.transAxes) 416 | fig.tight_layout() 417 | fig.subplots_adjust(left=0.15, hspace=0.0, wspace=0.0) 418 | fig.savefig(outfile) 419 | plt.close(fig) 420 | -------------------------------------------------------------------------------- /pySW4/postp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Postprocessing routines of pySW4 4 | """ 5 | from __future__ import absolute_import, print_function, division 6 | 7 | from .image import Image, read_image 8 | from .waveforms import Stations, read_stations 9 | -------------------------------------------------------------------------------- /pySW4/postp/waveforms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Routines for reading a line or an array of synthetic waveforms. 4 | 5 | .. module:: waveforms 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | :copyright: 11 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 12 | 13 | :license: 14 | This code is distributed under the terms of the 15 | GNU Lesser General Public License, Version 3 16 | (https://www.gnu.org/copyleft/lesser.html) 17 | 18 | 19 | .. rubric:: SW4 uses a right handed coordinate system 20 | 21 | 22 | :: 23 | 24 | X 25 | ⋰ 26 | ⋰ 27 | o------->Y 28 | | 29 | | 30 | V 31 | Z 32 | 33 | If the azimuth of the SW4 grid is ``0`` this translates to: 34 | 35 | - X == Northing 36 | - Y == Easting 37 | - Z == Vertical (inverted!) 38 | """ 39 | from __future__ import absolute_import, print_function, division 40 | 41 | import os 42 | from glob import glob 43 | import numpy as np 44 | from obspy import read, Stream 45 | from obspy.core.util import AttribDict 46 | from ..headers import STF, REC_MODE 47 | from ..plotting import plot_traces 48 | 49 | COORDINATE_SYSTEM = {'cartesian' : ('x','y'), 50 | 'geographical' : ('lon', 'lat')} 51 | 52 | 53 | def read_stations(name, path='./', mode='velocity', verbose=False): 54 | """ 55 | Read a single station or several stations along a line or as an 56 | array constructed using :func:`~pySW4.prep.stations.station_line` 57 | or :func:`~pySW4.prep.stations.station_array` in the preprocess 58 | phase. 59 | 60 | Parameters 61 | ---------- 62 | name : str 63 | For a single station give the name of the station used in the 64 | inputfile in the ``file`` field. For all stations relating to a 65 | line or array of stations use the prefix of the line or array 66 | name, should be equal to the value used for ``'name'`` in 67 | :func:`~pySW4.prep.stations.station_line` or 68 | :func:`~pySW4.prep.stations.station_array` in the preprocess 69 | phase. 70 | 71 | path : str 72 | Path (relative or absolute) to the files (default is './'). 73 | 74 | mode : {'displacement', 'velocity', 'div', 'curl', 'strains'} 75 | Mode of the ``rec`` files to be read. 76 | 77 | Returns 78 | ------- 79 | :class:`~.Stations` 80 | Class with waveform data and methods. 81 | """ 82 | return Stations(name, path, mode, verbose) 83 | 84 | 85 | class Stations(Stream): 86 | """ 87 | Class to handle waveform data from a line or an array of 88 | seismograms. 89 | 90 | Read a line or an array of stations constructed using 91 | :func:`~pySW4.prep.stations.station_line` or 92 | :func:`~pySW4.prep.stations.station_array` in the preprocess phase. 93 | 94 | Parameters 95 | ---------- 96 | name : str 97 | For a single station give the name of the station used in the 98 | inputfile in the ``file`` field. For all stations relating to a 99 | line or array of stations use the prefix of the line or array 100 | name, should be equal to the value used for ``'name'`` in 101 | :func:`~pySW4.prep.stations.station_line` or 102 | :func:`~pySW4.prep.stations.station_array` in the preprocess 103 | phase. 104 | 105 | path : str 106 | Path (relative or absolute) to the files (default is './'). 107 | 108 | mode : {'displacement', 'velocity', 'div', 'curl', 'strains'} 109 | Mode of the ``rec`` files to be read. 110 | """ 111 | def __init__(self, name=None, path='./', mode='velocity', verbose=False, 112 | traces=None): 113 | super(Stations, self).__init__(traces) 114 | 115 | try: 116 | self.name = name 117 | self.mode = mode 118 | 119 | files = glob(os.path.join(path, name) + '*' + REC_MODE[mode]) 120 | if len(files) < 1: 121 | msg = 'No files were found in {} with pattern {}*{}...' 122 | raise IOError(msg.format(path, name, REC_MODE[mode])) 123 | self.xi = [] 124 | self.yi = [] 125 | for i, f in enumerate(files): 126 | if verbose: 127 | print('Processing {}'.format(f)) 128 | 129 | # parse the filename to get the location of the trace 130 | try: 131 | (network, station, 132 | xcoor, x, ycoor, y, zcoor, 133 | z) = self._parse_rec_filename(f) 134 | except ValueError: 135 | try: 136 | network = f.rsplit('/', 1)[-1] 137 | network, station = network.rsplit('.', 1) 138 | xcoor = 'longitude' 139 | ycoor = 'latitude' 140 | zcoor = 'depth' 141 | except ValueError: 142 | station = None 143 | 144 | trace = read(f, format='SAC')[0] 145 | 146 | # add coordinates to the xi, yi vectors 147 | try: 148 | if x not in self.xi: 149 | self.xi += [x] 150 | if y not in self.yi: 151 | self.yi += [y] 152 | except NameError: 153 | x = trace.stats.sac.stlo 154 | y = trace.stats.sac.stla 155 | z = None 156 | 157 | # add coordinates AttribDict to the trace.stats object 158 | trace.stats.coordinates = AttribDict({ 159 | xcoor: x, 160 | ycoor: y, 161 | zcoor: z}) 162 | 163 | # update network and station names in trace stats 164 | trace.stats.network = network 165 | if station is None: 166 | station = str(i) 167 | trace.stats.station = station 168 | 169 | self.append(trace) 170 | 171 | if xcoor in (COORDINATE_SYSTEM['cartesian']): 172 | self.coordinate_system = 'cartesian' 173 | elif xcoor in (COORDINATE_SYSTEM['geographical']): 174 | self.coordinate_system = 'geographical' 175 | 176 | self.xi.sort() 177 | self.yi.sort() 178 | try: 179 | dx = np.gradient(self.xi)[0] 180 | dy = np.gradient(self.yi)[0] 181 | except ValueError: 182 | dx = 0 183 | dy = 0 184 | self.extent = (self.yi[0] - 0.5 * dy, self.yi[-1] + 0.5 * dy, 185 | self.xi[0] - 0.5 * dx, self.xi[-1] + 0.5 * dx) 186 | 187 | except AttributeError: 188 | pass 189 | 190 | def plot_traces(self, mode='', yscale='auto', hspace=0.2, 191 | wspace=0.05, figsize=None, fig=None, **kwargs): 192 | """ 193 | Plot all traces and their Fourier specra side-by-side. 194 | 195 | Parameters 196 | ---------- 197 | traces : :class:`~obspy.Stream` 198 | Traces to be plotted in an :class:`~obspy.Stream` object. 199 | 200 | mode : {'displacement', 'velocity', 'div', 'curl', 'strains'} 201 | Mode describes the type of data in traces. 202 | 203 | Optionaly, an alternative string can be given that will be used 204 | as the y-label of the time-histories. 205 | 206 | yscale : {'auto', 'all', 'normalize'} 207 | Set the scale of the vertical y-axis: 208 | 209 | - ``auto`` - Vertical scale of each axes is automatically set to 210 | the -\|max\| and \|max\| of each trace. 211 | 212 | - ``all`` - Vertical scale of all axes is set to the same limits 213 | which are the -\|max\| and \|max\| of all traces. 214 | 215 | - ``normalize`` - Each trace is normalized and plotted 216 | (``ylim=(-1, 1)``). 217 | 218 | 219 | hspace : float 220 | The hight space between axes. See 221 | :class:`~matplotlib.gridspec.GridSpec` documentation. 222 | 223 | wspace : float 224 | The width space between axes. See 225 | :class:`~matplotlib.gridspec.GridSpec` documentation. 226 | 227 | figsize : 2-tuple 228 | Size of the :class:`~matplotlib.figure.Figure`. 229 | 230 | fig : :class:`~matplotlib.figure.Figure` 231 | A :class:`~matplotlib.figure.Figure` instance. 232 | 233 | Other Parameters 234 | ---------------- 235 | kwargs : :func:`~matplotlib.pyplot.plot` propeties. 236 | """ 237 | return plot_traces(self, mode, yscale, hspace, wspace, figsize, fig, 238 | **kwargs) 239 | 240 | def times(self): 241 | return self.traces[0].times() 242 | 243 | @property 244 | def starttime(self): 245 | return self.traces[0].stats.starttime 246 | 247 | @property 248 | def delta(self): 249 | return self.traces[0].stats.delta 250 | 251 | def get_data(self, channel='*z'): 252 | """ 253 | Get data for the selected `channel`. Wildcard are also 254 | handled such that ``channel='*z'`` gets all vertical channels 255 | in *self.traces*. 256 | 257 | See :meth:`~obspy.core.stream.Stream.select` method for more 258 | info. 259 | 260 | Returns 261 | ------- 262 | 3D :class:`~numpy.ndarray` 263 | 3darray of shape (nx, ny, time). 264 | 265 | """ 266 | nx = len(self.xi) 267 | ny = len(self.yi) 268 | data = np.empty((nx, ny, self.times().size)) 269 | traces = self.select(channel=channel) 270 | 271 | xcoor = COORDINATE_SYSTEM[self.coordinate_system][0] 272 | ycoor = COORDINATE_SYSTEM[self.coordinate_system][1] 273 | 274 | for trace in traces: 275 | data[self.xi.index(trace.stats.coordinates[xcoor]), 276 | self.yi.index(trace.stats.coordinates[ycoor])] = trace.data 277 | return data 278 | 279 | def _parse_rec_filename(self, filename): 280 | """ 281 | Private method: Parse 'rec' filename and get the x, y or 282 | lat, lon coordinates. 283 | """ 284 | network, x, y, z = filename.rsplit('_', 4)[0:4] 285 | network = network.rsplit('/', 1)[-1] 286 | try: 287 | network, station = network.rsplit('.', 1) 288 | except ValueError: 289 | station = None 290 | 291 | xcoor, x = x.split('=') 292 | ycoor, y = y.split('=') 293 | zcoor, z = z.split('=') 294 | 295 | return (network, station, 296 | xcoor, float(x), ycoor, float(y), zcoor, float(z)) 297 | -------------------------------------------------------------------------------- /pySW4/prep/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Preprocessing routines of pySW4. 4 | """ 5 | from __future__ import absolute_import, print_function, division 6 | 7 | from . import material_model 8 | from . import source 9 | from . import stations 10 | from . import rfileIO 11 | -------------------------------------------------------------------------------- /pySW4/prep/material_model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Python module to help specify the material properties and the grid 4 | parameters of the computational domain. 5 | 6 | .. module:: material_model 7 | 8 | :author: 9 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 10 | 11 | :copyright: 12 | Shahar Shani-Kadmiel 13 | 14 | :license: 15 | This code is distributed under the terms of the 16 | GNU Lesser General Public License, Version 3 17 | (https://www.gnu.org/copyleft/lesser.html) 18 | """ 19 | from __future__ import absolute_import, print_function, division 20 | 21 | import os 22 | import numpy as np 23 | from scipy.interpolate import UnivariateSpline 24 | 25 | 26 | def get_vs(vp): 27 | """ 28 | Calculate Shear-wave velocity based on Brocher (2008). 29 | 30 | .. note:: 31 | Shear-wave velocity is forced to be greater than 32 | :math:`V_P / \\sqrt{2}`. 33 | 34 | Parameters 35 | ---------- 36 | vp : float or sequence 37 | Pressure-wave velocity in km/s. 38 | 39 | Returns 40 | ------- 41 | float or sequence 42 | Shear-wave velocity in km/s. 43 | """ 44 | # vs = (0.7858 45 | # - 1.2344 * vp 46 | # + 0.7949 * vp**2 47 | # - 0.1238 * vp**3 48 | # + 0.0064 * vp**4) 49 | 50 | # vs[vp <= 2] = vp[vp <= 2] / 1.73 51 | 52 | vs = vp / 1.73 53 | 54 | # try: 55 | # vs[vp / vs < 1.42] = vp[vp / vs < 1.42] / 1.4 56 | # except TypeError: 57 | # if vp / vs < 1.42: 58 | # print(vs) 59 | # vs = vp / 1.4 60 | # print(vs) 61 | return vs 62 | 63 | 64 | def get_rho(vp): 65 | """ 66 | Calculate :math:`\\rho` (density) based on Brocher (2008). 67 | 68 | Parameters 69 | ---------- 70 | vp : float or sequence 71 | Pressure-wave velocity in km/s. 72 | 73 | Returns 74 | ------- 75 | float or sequence 76 | :math:`\\rho` (density) in gr/cm^3. 77 | """ 78 | rho = (1.6612 * vp 79 | - 0.4721 * vp**2 80 | + 0.0671 * vp**3 81 | - 0.0043 * vp**4 82 | + 0.000106 * vp**5) 83 | return rho 84 | 85 | 86 | def get_qs(vs): 87 | """ 88 | Calculate Shear-wave Quality Factor based on Brocher (2008). 89 | 90 | .. note:: If Shear-wave velocity is less-than 0.3 km/s, Shear-wave 91 | Quality Factor is set to 13. 92 | 93 | Parameters 94 | ---------- 95 | vs : float or sequence 96 | Shear-wave velocity in km/s. 97 | 98 | Returns 99 | ------- 100 | float or sequence 101 | Shear-wave Quality Factor. 102 | """ 103 | qs = (-16 104 | + 104.13 * vs 105 | - 25.225 * vs**2 106 | + 8.2184 * vs**3) 107 | try: 108 | qs[vs < 0.3] = 13 109 | except TypeError: 110 | if vs < 0.3: 111 | qs = 13 112 | 113 | return qs 114 | 115 | 116 | def get_qp(qs): 117 | """Calculate Pressure-wave Quality Factor based on Brocher (2008). 118 | 119 | Parameters 120 | ---------- 121 | qs : float or sequence 122 | Shear-wave Quality Factor. 123 | 124 | Returns 125 | ------- 126 | float or sequence 127 | Pressure-wave Quality Factor. 128 | """ 129 | return 2 * qs 130 | 131 | 132 | def _sample_func(x, y): 133 | """ 134 | Helper function to interpolate between discrete ``x, y`` points. 135 | 136 | Used by the following methods 137 | - :meth:`~.V_model.get_properties` 138 | and 139 | - :meth:`~.V_model.get_depth` 140 | """ 141 | return UnivariateSpline(x, y, k=1, s=0) 142 | 143 | 144 | def read_Vfile(filename): 145 | """ 146 | Read a Velocity Model file. 147 | 148 | Vfile format has a flexible header (line starting with a '#' 149 | character) and a *comma separated values* data section where each 150 | line has *Depth*, *Vp*, *Vs*, *rho*, *Qp*, *Qs*, *Grp./Form./Mbr.* 151 | values and ends with a line containing one word 'end'. The values 152 | in each line are "true" to that specific depth-point. 153 | 154 | **Example Vfile:** 155 | :: 156 | 157 | # Just a simple made up velocity model 158 | # Shani-Kadmiel (2016) 159 | # Depth| Vp | Vs | rho | Qp | Qs | Grp/Form./Mbr. 160 | # m | m/s | m/s | kg/m^3| 161 | 0, 1000, 500, 1000, 20, 10, Upper made up 162 | 2000, 1500, 750, 1200, 30, 15, Middle made up 163 | 5000, 2000, 1000, 1600, 70, 35, Lower made up 164 | end 165 | 166 | Parameters 167 | ---------- 168 | filename : str 169 | Path (relative or absolute) to the Vfile. 170 | 171 | Returns 172 | ------- 173 | array-like 174 | A list of header, depth, vp, vs, rho, qp, qs, gmf values. 175 | 176 | See also 177 | -------- 178 | .V_model 179 | """ 180 | header = '' 181 | depth = [] 182 | vp = [] 183 | vs = [] 184 | rho = [] 185 | qp = [] 186 | qs = [] 187 | gmf = [] 188 | 189 | with open(filename, 'r') as f: 190 | for line in f: 191 | if line.startswith('#'): 192 | header += line 193 | elif line.startswith('\n'): 194 | continue 195 | elif line.startswith('end'): 196 | break 197 | else: 198 | try: 199 | fields = line.split(',') 200 | for i, f in enumerate(fields[:-1]): 201 | try: 202 | fields[i] = float(f) 203 | except ValueError: 204 | fields[i] = np.nan 205 | 206 | ldepth, lvp, lvs, lrho, lqp, lqs, lgmf = fields 207 | depth += [ldepth] 208 | vp += [lvp] 209 | vs += [lvs] 210 | rho += [lrho] 211 | qp += [lqp] 212 | qs += [lqs] 213 | gmf += [lgmf[:-1]] 214 | except ValueError: 215 | continue 216 | 217 | depth = np.array(depth, dtype=float) 218 | vp = np.array(vp, dtype=float) 219 | vs = np.array(vs, dtype=float) 220 | rho = np.array(rho, dtype=float) 221 | qp = np.array(qp, dtype=float) 222 | qs = np.array(qs, dtype=float) 223 | 224 | return header, depth, vp, vs, rho, qp, qs, gmf 225 | 226 | 227 | class V_model(): 228 | """ 229 | Class to handle Velocity models. 230 | 231 | Uses :func:`~.read_Vfile` function to read Vfile format files. 232 | 233 | Parameters 234 | ---------- 235 | filename : str 236 | Path (relative or absolute) to the Vfile. 237 | 238 | calculate_missing_data : bool 239 | If ``True`` (default), any missing data is calculated based on 240 | Brocher (2008). 241 | """ 242 | def __init__(self, filename, calculate_missing_data=True): 243 | self.name = os.path.splitext(os.path.split(filename)[-1])[0] 244 | self.filename = os.path.abspath(filename) 245 | 246 | (self.header, 247 | self.depth, 248 | self.vp, 249 | self.vs, 250 | self.rho, 251 | self.qp, 252 | self.qs, 253 | self.gmf) = read_Vfile(self.filename) 254 | 255 | if calculate_missing_data: 256 | self._calculate_missing_data() 257 | 258 | def _calculate_missing_data(self): 259 | """ 260 | Looks for ``nan`` s in the data and trys to calculate and fill 261 | based on Brocher (2008). 262 | """ 263 | if np.isnan(self.vs).any(): 264 | indices = np.isnan(self.vs) 265 | self.vs[indices] = ( 266 | get_vs(self.vp[indices] * 1e-3) * 1e3).round() 267 | 268 | if np.isnan(self.rho).any(): 269 | indices = np.isnan(self.rho) 270 | self.rho[indices] = ( 271 | get_rho(self.vp[indices] * 1e-3) * 1e3).round() 272 | 273 | if np.isnan(self.qs).any(): 274 | indices = np.isnan(self.qs) 275 | self.qs[indices] = ( 276 | get_qs(self.vs[indices] * 1e-3)).round() 277 | 278 | if np.isnan(self.qp).any(): 279 | indices = np.isnan(self.qp) 280 | self.qp[indices] = ( 281 | get_qp(self.qs[indices])).round() 282 | 283 | def get_properties(self, depth, k=0): 284 | """ 285 | This function first fits the data in the velocity model 286 | with an interpolation function using :func:`~._sample_func` and 287 | then evaluates the properties at the requested ``depths``. 288 | Interpolation can either be based on piecewise step functions 289 | that uses the nearest value or on a linear interpolation. 290 | 291 | Parameters 292 | ---------- 293 | depth : sequence or int or float 294 | Depths at which to evaluate the properties. 295 | 296 | k : int 297 | Interpolation method: 298 | 299 | - 0 - nearest value (default) 300 | - 1 - linear interpolation between data points 301 | 302 | Returns 303 | ------- 304 | array-like 305 | vp, vs, rho, qp, qs 306 | """ 307 | 308 | properties = [self.vp, self.vs, self.rho, self.qp, self.qs] 309 | new_properties = [] 310 | if k == 0: 311 | for p in properties: 312 | func = _sample_func(self.depth, p) 313 | new_properties += [func(depth)] 314 | elif k == 1: 315 | for p in properties: 316 | func = _sample_func(self.depth[::2], p[::2]) 317 | new_properties += [func(depth)] 318 | 319 | return new_properties 320 | 321 | def get_depth(self, values, property='vp'): 322 | """ 323 | This function linearly interpolats the data in the velocity 324 | model and then evaluates the depth corresponding to the value of 325 | the requested property. 326 | 327 | Parameters 328 | ---------- 329 | values : sequence or int or float 330 | The property for which to evaluate the depth of. 331 | 332 | property : {'vp' (default), 'vs', 'rho, 'qp', 'qs'} 333 | Property corresponding to `value`. 334 | 335 | Returns 336 | ------- 337 | sequence or float 338 | Depth of the requested property. 339 | """ 340 | 341 | if property is 'vp': 342 | p = self.vp 343 | elif property is 'vs': 344 | p = self.vs 345 | elif property is 'rho': 346 | p = self.rho 347 | elif property is 'qp': 348 | p = self.qp 349 | elif property is 'qs': 350 | p = self.qs 351 | 352 | func = _sample_func(p[::2], self.depth[::2]) 353 | depth = func(values) 354 | 355 | return depth 356 | 357 | def __str__(self): 358 | out = self.header 359 | for i in range(self.depth.size): 360 | out += ('%7d,%7d,%7d,%7d,%7d,%7d,%s\n' 361 | % (self.depth[i], self.vp[i], self.vs[i], self.rho[i], 362 | self.qp[i], self.qs[i], self.gmf[i])) 363 | return out 364 | 365 | def write2file(self, filename=None): 366 | """ 367 | Save Vfile for reading later into :class:`~.Vfile` class. 368 | """ 369 | if filename is None: 370 | filename = self.filename 371 | with open(filename, 'w') as f: 372 | f.write(self.__str__()) 373 | f.write('end\n') 374 | 375 | 376 | def grid_spacing(vmin, fmax, ppw=8): 377 | """ 378 | Calculate the ``h`` parameter (``grid_spacing``) based on the 379 | requirement that the shortest wavelength 380 | (:math:`\\lambda=V_{min}/f_{max}`) be sampled by a minimum 381 | points-per-wavelength (``ppw``). 382 | 383 | Parameters 384 | ---------- 385 | vmin : float 386 | Minimum seismic velocity in the computational doamin in m/s. 387 | 388 | fmax : float 389 | Maximum frequency in the source-time function frequency content, 390 | Hz. 391 | 392 | ppw : 393 | Minimum points-per-wavelenght required for the computation. Set 394 | to 8 by default. 395 | 396 | Returns 397 | ------- 398 | float 399 | The suggested grid spacing in meters. 400 | 401 | See also 402 | -------- 403 | .get_vmin 404 | .source_frequency 405 | .f_max 406 | """ 407 | return float(vmin) / (fmax * ppw) 408 | 409 | 410 | def get_vmin(h, fmax, ppw=8): 411 | """ 412 | Calculate the minimum allowed velocity that meets the requirement 413 | that the shortest wavelength (:math:`\\lambda=V_{min}/f_{max}`) be 414 | sampled by a minimum points-per-wavelength (`ppw`). 415 | 416 | Parameters 417 | ---------- 418 | h : float 419 | Grid spacing of the computational doamin in meters. 420 | 421 | fmax : float 422 | Maximum frequency in the source-time function frequency content, 423 | Hz. 424 | 425 | ppw : 426 | Minimum points-per-wavelenght required for the computation. Set 427 | to 8 by default. 428 | 429 | Returns 430 | ------- 431 | float 432 | The suggested grid spacing in meters. 433 | 434 | See also 435 | -------- 436 | .grid_spacing 437 | .source_frequency 438 | .f_max 439 | """ 440 | return h * fmax * ppw 441 | 442 | 443 | # def get_z(v, v0, v_grad): 444 | # return (v - v0) / v_grad 445 | -------------------------------------------------------------------------------- /pySW4/prep/source.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Python module to help specify the source parameters. 4 | 5 | .. module:: source 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | :copyright: 11 | Shahar Shani-Kadmiel 12 | 13 | :license: 14 | This code is distributed under the terms of the 15 | GNU Lesser General Public License, Version 3 16 | (https://www.gnu.org/copyleft/lesser.html) 17 | """ 18 | from __future__ import absolute_import, print_function, division 19 | 20 | import os 21 | import numpy as np 22 | from ..headers import STF 23 | 24 | 25 | def Mw(M0): 26 | """ 27 | Calculate Moment Magnitude Mw from Seismic Moment M0 in N m 28 | """ 29 | return 2. / 3 * (np.log10(M0) - 9.1) 30 | 31 | 32 | def M0(Mw): 33 | """ 34 | Calculate Seismic Moment M0 in N m from Moment Magnitude Mw 35 | """ 36 | return 10**(1.5 * Mw + 9.1) 37 | 38 | 39 | def source_frequency(fmax, stf='Gaussian'): 40 | """ 41 | Calculate the angular frequency :math:`\omega`, ``freq`` attribute 42 | on the source line in the SW4 inputfile. 43 | 44 | Parameters 45 | ---------- 46 | fmax : float 47 | Maximum frequency in the source-time function frequency content, 48 | Hz. 49 | 50 | stf : str 51 | Source-time function name. It can have the following values: 52 | 'GaussianInt', 'Gaussian' (**default**), 'RickerInt', 'Ricker', 53 | 'Ramp', 'Triangle', 'Sawtooth', 'Smoothwave', 'VerySmoothBump', 54 | 'Brune', 'BruneSmoothed', 'GaussianWindow', 'Liu', 'Dirac', and 55 | 'C6SmoothBump'. 56 | 57 | See the `SW4 User Guide 58 | `_ for further 59 | details. 60 | 61 | Returns: 62 | 2-tuple 63 | ``f0``, ``freq``. ``freq`` is the value which goes on the source 64 | line in the SW4 inputfile. 65 | """ 66 | 67 | f0 = fmax / STF[stf].fmax2f0 # fundamental freqency, Hz 68 | freq = f0 * STF[stf].freq2f0 # angular frequency, rad/s 69 | 70 | return f0, freq 71 | 72 | 73 | def t0(freq, t0=0.0, stf='Gaussian'): 74 | """ 75 | Calculate the ``t0`` attribute on the source line in the SW4 76 | inputfile. 77 | 78 | Parameters 79 | ---------- 80 | freq : float 81 | The angular frequency value used for the ``freq`` attribute on 82 | the source line in the SW4 inputfile. 83 | 84 | t0 : float 85 | The calculated ``t0`` is added to the supllied `t0` in the 86 | function call. 87 | 88 | stf : str 89 | The source-time function name. 90 | """ 91 | if 'Gaussian' in stf: 92 | t0 += 6. / freq 93 | elif 'Ricker' in stf: 94 | t0 += 6. * 2 * np.pi / freq 95 | return t0 * 1.1 # take that extra 10% 96 | 97 | 98 | def gaussian_stf(time, t0, freq): 99 | """ 100 | Gaussian source-time-function. 101 | """ 102 | return freq / np.sqrt(2 * np.pi) * np.exp(-0.5 * (freq * (time - t0)**2)) 103 | 104 | 105 | def f_max(vmin, h, ppw=8): 106 | """ 107 | Calculate the maximum resolved frequency that meets the requirement 108 | that the shortest wavelength (:math:`\lambda=V_{min}/f_{max}`) be 109 | sampled by a minimum points-per-wavelength (`ppw`). 110 | 111 | Parameters 112 | ---------- 113 | vmin : float 114 | Minimum seismic velocity in the computational doamin in m/s. 115 | 116 | h : float 117 | Grid spacing of the computational doamin in meters. 118 | 119 | ppw : 120 | Minimum points-per-wavelenght required for the computation. Set 121 | to 8 by default. 122 | 123 | Returns 124 | ------- 125 | float 126 | The suggested ``fmax`` in Hz. 127 | 128 | See also 129 | -------- 130 | .grid_spacing 131 | .get_vmin 132 | .source_frequency 133 | """ 134 | return float(vmin) / (h * ppw) 135 | -------------------------------------------------------------------------------- /pySW4/prep/stations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Python module for placing stations to record synthetic seismograms in 4 | SW4 simulations. 5 | 6 | .. module:: stations 7 | 8 | :author: 9 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 10 | 11 | :copyright: 12 | Shahar Shani-Kadmiel 13 | 14 | :license: 15 | This code is distributed under the terms of the 16 | GNU Lesser General Public License, Version 3 17 | (https://www.gnu.org/copyleft/lesser.html) 18 | """ 19 | from __future__ import absolute_import, print_function, division 20 | 21 | import os 22 | import shutil 23 | import numpy as np 24 | from obspy import read_inventory, Inventory 25 | from warnings import warn 26 | 27 | 28 | def _append2infile(infile, name, string): 29 | """ 30 | Helper function. Copy an existing SW4 input file, add `name` 31 | to the filename, open the new file in append mode and write the 32 | string to the end of the file. 33 | """ 34 | if name.join(('.', '.')) not in infile: 35 | filename, extention = os.path.splitext(infile) 36 | filename += '.' + name + extention 37 | shutil.copyfile(infile, filename) 38 | else: 39 | filename = infile 40 | 41 | with open(filename, "a") as f: 42 | f.write(string) 43 | f.close() 44 | 45 | return filename 46 | 47 | 48 | def station_array(x1=None, x2=None, y1=None, y2=None, 49 | lon1=None, lon2=None, lat1=None, lat2=None, 50 | depth=0, number_of_stations=None, spacing=None, 51 | name='array', mode='displacement', writeEvery=100, 52 | nsew=0, fmt='%.3f', infile=None): 53 | 54 | """ 55 | Place stations to record synthetics on a line. 56 | 57 | Corners of the array can be given in - 58 | 59 | **Catersian grid coordinates:** 60 | 61 | `x1`, `y1`, `x2`, `y2` 62 | 63 | or in - 64 | 65 | **Geographical coordinates:** 66 | 67 | `lon1`, `lat1`, `lon2`, `lat2` 68 | 69 | Parameters 70 | ---------- 71 | depth : float 72 | Depth of the stations in the array. 73 | 74 | number_of_stations : int or tuple or None 75 | If `int`, number of stations is the same in both directions. To 76 | set a different number of stations in each direction provide a 77 | `tuple`. If ``None``, the `spacing` argument is expected. 78 | 79 | spacing : float or tuple or None 80 | If `float`, spacing between stations is taken to be the same in 81 | both directions. To set different spacing in each direction 82 | provide a `tuple`. If ``None``, the `number_of_stations` 83 | argument is expected. 84 | 85 | `spacing` must be given in meters if Cartesian grid 86 | coordinates are used or in decimal degrees if Geographical 87 | coordinates are used. 88 | 89 | name : str 90 | Prefix name of the sac file name. 91 | 92 | mode : {'displacement' (default), 'velocity', 'div', 'curl', 'strains'} 93 | Mode of the recorded motions. 94 | 95 | writeEvery : int 96 | Cycle interval to write the data to disk. 97 | 98 | nsew : int 99 | The components of the station: 100 | 101 | - 0 - x, y, z (default), or 102 | - 1 - East, North, Vertical 103 | 104 | fmt : str 105 | Format of the coordinates, default is '%.3f'. 106 | 107 | infile : str 108 | Path (relative or absolute) to the SW4 inputfile. Stations are 109 | added to a copy of the specified file and saved with the string 110 | `name` appended to the `infile` name. 111 | (`name` is 'array' by default). 112 | 113 | If set to ``None``, a formated string is returned that can then 114 | be copied manually to an input file. 115 | 116 | Returns 117 | ------- 118 | str 119 | Filename of the newly saved inputfile or a formatted string that 120 | can be copied manually to an SW4 inputfile. 121 | """ 122 | if not number_of_stations and not spacing: 123 | raise ValueError( 124 | 'Either ``number_of_stations`` or ``spacing`` must be specified!' 125 | ) 126 | elif number_of_stations and spacing: 127 | warnings.warn( 128 | 'Both ``number_of_stations`` and ``spacing`` are specified, ' 129 | 'using ``number_of_stations={}``.'.format(number_of_stations)) 130 | elif number_of_stations: 131 | try: 132 | number_of_stations = int(number_of_stations) 133 | number_of_stations = (number_of_stations, number_of_stations) 134 | except TypeError: 135 | pass 136 | elif spacing: 137 | try: 138 | spacing = float(spacing) 139 | spacing = (spacing, spacing) 140 | except TypeError: 141 | pass 142 | 143 | if x1 is not None and x2 is not None: 144 | try: 145 | x = np.linspace(x1, x2, number_of_stations[0]) 146 | y = np.linspace(y1, y2, number_of_stations[1]) 147 | except TypeError: 148 | x = np.arange(x1, x2, spacing[0]) 149 | y = np.arange(y1, y2, spacing[1]) 150 | 151 | sac_string = ('rec x={0} y={0} depth={0} ' 152 | 'file=%s_x={0}_y={0}_z={0}_ ' 153 | 'writeEvery=%d nsew=%d variables=%s\n').format(fmt) 154 | 155 | elif lon1 is not None and lon2 is not None: 156 | try: 157 | x = np.linspace(lon1, lon2, number_of_stations[0]) 158 | y = np.linspace(lat1, lat2, number_of_stations[1]) 159 | except TypeError: 160 | x = np.arange(lon1, lon2, spacing[0]) 161 | y = np.arange(lat1, lat2, spacing[1]) 162 | 163 | sac_string = ('rec lon={0} lat={0} depth={0} ' 164 | 'file=%s_lon={0}_lat={0}_z={0}_ ' 165 | 'writeEvery=%d nsew=%d variables=%s\n').format(fmt) 166 | 167 | points = [(i, j) for j in y for i in x] 168 | 169 | string = ('\n\n# %dx%d array of seismograms added: %s\n' 170 | % (len(x), len(y), name)) 171 | 172 | for (i, j) in points: 173 | string += (sac_string % (i, j, depth, name, i, j, depth, 174 | writeEvery, nsew, mode)) 175 | 176 | if infile is None: 177 | return string 178 | else: 179 | return _append2infile(infile, name, string) 180 | 181 | 182 | def station_line(x1=None, x2=None, y1=None, y2=None, 183 | lon1=None, lon2=None, lat1=None, lat2=None, 184 | depth1=0, depth2=0, number_of_stations=3, 185 | name='line', mode='displacement', writeEvery=100, 186 | nsew=0, fmt='%.3f', infile=None): 187 | """ 188 | Place stations to record synthetics on a line. 189 | 190 | Start- and end-point can be given in - 191 | 192 | **Catersian grid coordinates:** 193 | 194 | `x1`, `y1`, `depth1`, `x2`, `y2`, `depth2` 195 | 196 | or in - 197 | 198 | **Geographical coordinates:** 199 | 200 | `lon1`, `lat1`, `depth1`, `lon2`, `lat2`, `depth2` 201 | 202 | Parameters 203 | ---------- 204 | number_of_stations : int 205 | Number of stations to place on the line (defaults to 3 - one at 206 | each end and another one in the middle). 207 | 208 | name : str 209 | Prefix name of the sac file name. 210 | 211 | mode : {'displacement' (default), 'velocity', 'div', 'curl', 'strains'} 212 | Mode of the recorded motions. 213 | 214 | writeEvery : int 215 | Cycle interval to write the data to disk. 216 | 217 | nsew : int 218 | The components of the station: 219 | 220 | - 0 - x, y, z (default), or 221 | - 1 - East, North, Vertical 222 | 223 | fmt : str 224 | Format of the coordinates, default is '%.3f'. 225 | 226 | infile : str 227 | Path (relative or absolute) to the SW4 inputfile. Stations are 228 | added to a copy of the specified file and saved with the string 229 | `name` appended to the `infile` name. 230 | (`name` is 'array' by default). 231 | 232 | If set to ``None``, a formated string is returned that can then 233 | be copied manually to an input file. 234 | 235 | Returns 236 | ------- 237 | str 238 | Filename of the newly saved inputfile or a formatted string that 239 | can be copied manually to an SW4 inputfile. 240 | """ 241 | 242 | if x1 is not None and x2 is not None: 243 | x = np.linspace(x1, x2, number_of_stations) 244 | y = np.linspace(y1, y2, number_of_stations) 245 | 246 | sac_string = ('rec x={0} y={0} depth={0} ' 247 | 'file=%s_x={0}_y={0}_z={0}_ ' 248 | 'writeEvery=%d nsew=%d variables=%s\n').format(fmt) 249 | 250 | elif lon1 is not None and lon2 is not None: 251 | x = np.linspace(lon1, lon2, number_of_stations) 252 | y = np.linspace(lat1, lat2, number_of_stations) 253 | 254 | sac_string = ('rec lon={0} lat={0} depth={0} ' 255 | 'file=%s_lon={0}_lat={0}_z={0}_ ' 256 | 'writeEvery=%d nsew=%d variables=%s\n').format(fmt) 257 | 258 | z = np.linspace(depth1, depth2, number_of_stations) 259 | 260 | string = '\n\n# stations on a line: %s\n' % name 261 | for i in range(len(x)): 262 | string += (sac_string % (x[i], y[i], z[i], name, 263 | x[i], y[i], z[i], writeEvery, nsew, mode)) 264 | 265 | if infile is None: 266 | return string 267 | else: 268 | return _append2infile(infile, name, string) 269 | 270 | 271 | def station_location(x=None, y=None, lat=None, lon=None, depth=0, 272 | name='st', mode='displacement', writeEvery=100, 273 | nsew=0, fmt='%.3f', infile=None): 274 | """ 275 | Place stations to record synthetics at specific locations. 276 | 277 | Can handle a single station or a list of stations. If several 278 | stations are passes, `x, y` (or `lat, lon`), and `name` must 279 | be the same length. 280 | 281 | Locations may be given in - 282 | 283 | **Catersian grid coordinates:** 284 | 285 | `x`, `y`, `depth` 286 | 287 | or in - 288 | 289 | **Geographical coordinates:** 290 | 291 | `lon`, `lat`, `depth` 292 | 293 | Parameters 294 | ---------- 295 | name : str 296 | Prefix name of the sac file name. 297 | 298 | mode : {'displacement' (default), 'velocity', 'div', 'curl', 'strains'} 299 | Mode of the recorded motions. 300 | 301 | writeEvery : int 302 | Cycle interval to write the data to disk. 303 | 304 | nsew : int 305 | The components of the station: 306 | 307 | - 0 - x, y, z (default), or 308 | - 1 - East, North, Vertical 309 | 310 | fmt : str 311 | Format of the coordinates, default is '%.3f'. 312 | 313 | infile : str 314 | Path (relative or absolute) to the SW4 inputfile. Stations are 315 | added to a copy of the specified file and saved with the string 316 | `name` appended to the `infile` name. 317 | (`name` is 'array' by default). 318 | 319 | If set to ``None``, a formated string is returned that can then 320 | be copied manually to an input file. 321 | 322 | Returns 323 | ------- 324 | str 325 | Filename of the newly saved inputfile or a formatted string that 326 | can be copied manually to an SW4 inputfile. 327 | """ 328 | 329 | if x is not None and y is not None: 330 | sac_string = ('rec x={0} y={0} depth={0} file=%s ' 331 | 'writeEvery=%d nsew=%d variables=%s\n').format(fmt) 332 | elif lon is not None and lat is not None: 333 | x, y = lon, lat 334 | sac_string = ('rec lon={0} lat={0} depth={0} file=%s ' 335 | 'writeEvery=%d nsew=%d variables=%s\n').format(fmt) 336 | 337 | string = '\n\n# stations at locations:\n' 338 | try: 339 | for i in range(len(x)): 340 | string += (sac_string % (float(x[i]), float(y[i]), depth, 341 | name[i], writeEvery, nsew, mode)) 342 | except TypeError: 343 | string += (sac_string % (float(x), float(y), depth, name, 344 | writeEvery, nsew, mode)) 345 | if infile is None: 346 | return string 347 | else: 348 | return _append2infile(infile, name, string) 349 | 350 | 351 | def inventory2station_locations(inv, mode='displacement', writeEvery=100, 352 | name='st', nsew=0, fmt='%.3f', infile=None): 353 | """ 354 | Place stations to record synthetics at specific locations. 355 | 356 | Extracts station locations from an 357 | :class:`~obspy.core.inventory.inventory.Inventory` object and 358 | generate an SW4 inputfile string. 359 | 360 | Parameters 361 | ---------- 362 | 363 | inv : str or :class:`~obspy.core.inventory.inventory.Inventory` 364 | Path to a StationXML file or an 365 | :class:`~obspy.core.inventory.inventory.Inventory` object. 366 | 367 | mode : {'displacement' (default), 'velocity', 'div', 'curl', 'strains'} 368 | Mode of the recorded motions. 369 | 370 | writeEvery : int 371 | Cycle interval to write the data to disk. 372 | 373 | nsew : int 374 | The components of the station: 375 | 376 | - 0 - x, y, z (default), or 377 | - 1 - East, North, Vertical 378 | 379 | fmt : str 380 | Format of the coordinates, default is '%.3f'. 381 | 382 | infile : str 383 | Path (relative or absolute) to the SW4 inputfile. Stations are 384 | added to a copy of the specified file and saved with the string 385 | `name` appended to the `infile` name. 386 | (`name` is 'array' by default). 387 | 388 | If set to ``None``, a formated string is returned that can then 389 | be copied manually to an input file. 390 | 391 | Returns 392 | ------- 393 | str 394 | Filename of the newly saved inputfile or a formatted string that 395 | can be copied manually to an SW4 inputfile. 396 | """ 397 | 398 | if not isinstance(inv, Inventory): 399 | inv = read_inventory(inv) 400 | 401 | sac_string = ('rec lon={0} lat={0} depth=0 file=%s ' 402 | 'writeEvery=%d nsew=%d variables=%s\n').format(fmt) 403 | 404 | string = '\n\n# stations at locations:\n' 405 | for net in inv.networks: 406 | for sta in net.stations: 407 | string += (sac_string % (sta.longitude, sta.latitude, 408 | '.'.join((net.code, sta.code)), 409 | writeEvery, nsew, mode)) 410 | if infile is None: 411 | return string 412 | else: 413 | return _append2infile(infile, name, string) 414 | -------------------------------------------------------------------------------- /pySW4/sw4_metadata.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Parsing routines for SW4 input and output files and directories. 4 | 5 | .. module:: sw4_metadata 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | Omry Volk (omryv@post.bgu.ac.il) 11 | 12 | Tobias Megies (megies@geophysik.uni-muenchen.de) 13 | 14 | :copyright: 15 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 16 | 17 | Omry Volk (omryv@post.bgu.ac.il) 18 | 19 | Tobias Megies (megies@geophysik.uni-muenchen.de) 20 | 21 | :license: 22 | This code is distributed under the terms of the 23 | GNU Lesser General Public License, Version 3 24 | (https://www.gnu.org/copyleft/lesser.html) 25 | """ 26 | from __future__ import absolute_import, print_function, division 27 | 28 | import os 29 | from warnings import warn 30 | from obspy.core.util import AttribDict 31 | import numpy as np 32 | from .utils import nearest_values, geographic2cartesian 33 | 34 | 35 | class Inputfile(AttribDict): 36 | """ 37 | A container for the simulation metadata parsed from the SW4 38 | inputfile. 39 | 40 | Parameters 41 | ---------- 42 | filename : str or :class:`~obspy.core.util.attribdict.AttribDict` 43 | Path (relative or absolute) of an SW4 input file. 44 | """ 45 | def __init__(self, filename): 46 | if type(filename) is str: 47 | input_ = AttribDict() 48 | with open(filename) as fh: 49 | for line in fh: 50 | # get rid of comments 51 | line = line.split("#")[0].strip() 52 | if not line: 53 | continue 54 | line = line.split() 55 | input_category = input_.setdefault(line.pop(0), []) 56 | input_item = AttribDict() 57 | for item in line: 58 | key, value = item.split("=", 1) 59 | input_item[key] = _decode_string_value(value) 60 | input_category.append(input_item) 61 | else: 62 | input_ = filename 63 | super(Inputfile, self).__init__(input_) 64 | self.get_Proj4() 65 | 66 | def get_Proj4(self): 67 | """ 68 | Parse the ``grid`` line and figure out if a Proj4 projection 69 | was used. 70 | """ 71 | proj_dict = AttribDict() 72 | try: 73 | proj_dict['proj'] = self.grid[0]['proj'] 74 | proj = True 75 | except KeyError: 76 | # warn('No proj found... setting to ``utm``.') 77 | proj_dict['proj'] = 'utm' 78 | proj = False 79 | 80 | try: 81 | proj_dict['ellps'] = self.grid[0]['ellps'] 82 | ellps = True 83 | except KeyError: 84 | if 'datum' in self.grid[0]: 85 | # warn('No ellps found, but datum was....') 86 | pass 87 | else: 88 | # warn('No ellps found... setting to ``WGS84``.') 89 | proj_dict['ellps'] = 'WGS84' 90 | ellps = False 91 | 92 | try: 93 | proj_dict['datum'] = self.grid[0]['datum'] 94 | datum = True 95 | except KeyError: 96 | # warn('No datum found... ' 97 | # 'setting to ``{}``.'.format(proj_dict['ellps'])) 98 | proj_dict['datum'] = proj_dict['ellps'] 99 | datum = False 100 | 101 | try: 102 | proj_dict['lon_0'] = self.grid[0]['lon_p'] 103 | lon_p = True 104 | except KeyError: 105 | # warn('No lon_p found... ' 106 | # 'setting to ``{}``.'.format(self.grid[0]['lon'])) 107 | proj_dict['lon_0'] = self.grid[0]['lon'] 108 | lon_p = False 109 | 110 | try: 111 | proj_dict['lat_0'] = self.grid[0]['lat_p'] 112 | lat_p = True 113 | except KeyError: 114 | # warn('No lat_p found... ' 115 | # 'setting to ``{}``.'.format(self.grid[0]['lat'])) 116 | proj_dict['lat_0'] = self.grid[0]['lat'] 117 | lat_p = False 118 | 119 | try: 120 | proj_dict['scale'] = self.grid[0]['scale'] 121 | scale = True 122 | except KeyError: 123 | # warn('No scale found... setting to ``None``.') 124 | # proj_dict['scale'] = self.grid[0]['lat'] 125 | scale = False 126 | 127 | if proj or ellps or datum or lon_p or lat_p or scale: 128 | self.is_proj4 = True 129 | self.proj4 = proj_dict 130 | else: 131 | self.is_proj4 = False 132 | self.proj4 = None 133 | 134 | def get_coordinates(self, key, xi=None, elev=None, 135 | plane=0, coordinate=0, 136 | distance=np.inf): 137 | """ 138 | Gets coordinates for input keys that have 3D coordinates: 139 | 140 | **Catersian grid coordinates:** 141 | 142 | `x`, `y`, `z` (or `depth`) 143 | 144 | or in - 145 | 146 | **Geographical coordinates:** 147 | 148 | `lon`, `lat`, `depth` (or `z`) 149 | 150 | Parameters 151 | ---------- 152 | key : str 153 | Keyword to look for. 154 | 155 | xi : array-like 156 | `x` coordinate along the cross-section 157 | 158 | elev : array-like 159 | Elevation of the top surface. Same size as `xi`. 160 | Used to correct the returned y-plotting coordinate if 161 | `depth` is encounterd. 162 | 163 | plane : int 164 | Indicates cross-section (``0`` or ``1``) or map (``2``). 165 | 166 | coordinate : float 167 | Plane coordinate. 168 | 169 | distance : float 170 | Threshold distance from the plane coordinate to include. By 171 | default everything is included but this can cause too many 172 | symbols to be plotted obscuring the image. 173 | 174 | Returns 175 | ------- 176 | 2-sequence 177 | x- and y-plotting coordinates. 178 | 179 | Examples 180 | -------- 181 | >>> get_coordinates('source') 182 | 183 | for easy plotting with :meth:`~pySW4.postp.image.Image.plot`. 184 | """ 185 | items = self.get(key, []) 186 | if not items: 187 | return None 188 | x = [] 189 | y = [] 190 | z = [] 191 | 192 | for item in items: 193 | try: 194 | x_ = item.x 195 | y_ = item.y 196 | try: 197 | z_ = item.z 198 | except AttributeError: 199 | z_ = item.depth 200 | except AttributeError: 201 | # warn('NotImplementedError: ' + msg.format(key, item)) 202 | # continue 203 | x_ = item.lon 204 | y_ = item.lat 205 | try: 206 | z_ = item.z 207 | except AttributeError: 208 | z_ = item.depth 209 | 210 | # geographical ===> cartesian coordinates 211 | x_, y_ = geographic2cartesian( 212 | self.proj4, lon0=self.grid[0].lon, lat0=self.grid[0].lat, 213 | az=self.grid[0].az, lon=x_, lat=y_, 214 | m_per_lat=self.grid[0].get('mlat', 111319.5), 215 | m_per_lon=self.grid[0].get('mlon')) 216 | 217 | try: 218 | x += [x_] 219 | y += [y_] 220 | z += [z_] 221 | except UnboundLocalError: 222 | continue 223 | if not x: 224 | return None 225 | 226 | x = np.array(x) 227 | y = np.array(y) 228 | z = np.array(z) 229 | 230 | if plane == 0: 231 | nearest = nearest_values(x, coordinate, distance) 232 | x, y, z = x[nearest], y[nearest], z[nearest] 233 | idx = xi.searchsorted(y) 234 | z_cor = elev[idx] + z 235 | return xi[idx], z_cor 236 | elif plane == 1: 237 | nearest = nearest_values(y, coordinate, distance) 238 | x, y, z = x[nearest], y[nearest], z[nearest] 239 | idx = xi.searchsorted(x) 240 | z_cor = elev[idx] + z 241 | return xi[idx], z_cor 242 | elif plane == 2: 243 | nearest = nearest_values(z, coordinate, distance) 244 | x, y, z = x[nearest], y[nearest], z[nearest] 245 | return y, x 246 | 247 | 248 | class Outputfile(AttribDict): 249 | """ 250 | A container for simulation metadata parsed from the SW4 STDOUT 251 | saved to a file. 252 | 253 | **The keywords the parser looks for are:** 254 | 255 | - 'Grid' - Information about the grid discretization. 256 | - 'Receiver INFO' - Cartesian (x, y, z) coordinates of recievers placed with geographical (lon, lat, z or depth) coordinates. 257 | - 'Geographic and Cartesian' - corners of the computational grid. 258 | - 'Start Time' - of the simulation. 259 | - 'Goal Time' - time to simulate. 260 | - 'Number of time steps' - steps to compute. 261 | - 'dt' - size of each time step 262 | - 'Total seismic moment' - sum of all sources in the simulation. 263 | - 'Moment magnitude' - Moment magnitude based on :math:`M_0` . 264 | 265 | Parameters 266 | ---------- 267 | filename : str or :class:`~obspy.core.util.attribdict.AttribDict` 268 | Path (relative or absolute) of an SW4 input file. 269 | """ 270 | def __init__(self, filename): 271 | if type(filename) is str: 272 | output_ = AttribDict() 273 | 274 | grid = output_.setdefault('grid', []) 275 | reciever = output_.setdefault('reciever', []) 276 | corners = output_.setdefault('corners', []) 277 | 278 | fh = open(filename, 'r') 279 | while True: 280 | line = fh.readline() 281 | if not line: 282 | fh.close() 283 | break 284 | else: 285 | line = line.strip() 286 | 287 | if 'reading input file' in line: 288 | output_.read_input_file_phase = line.split( 289 | 'reading input file')[1] 290 | continue 291 | 292 | if 'start up phase' in line: 293 | output_.start_up_phase = line.split('start up phase')[1] 294 | continue 295 | 296 | if 'solver phase' in line: 297 | output_.solver_phase = line.split('solver phase')[1] 298 | continue 299 | 300 | if line.startswith('Grid'): 301 | line = line.split() 302 | keys = line 303 | while True: 304 | line = fh.readline() 305 | if line.startswith('Total'): 306 | grid.append( 307 | AttribDict( 308 | {'points': int(float(line.split()[-1]))})) 309 | break 310 | else: 311 | line = line.split() 312 | grid_i = AttribDict() 313 | for k, v in zip(keys, line): 314 | grid_i[k] = int(v) 315 | grid.append(grid_i) 316 | 317 | if line.startswith('Receiver INFO'): 318 | name = line.split()[-1][:-1] 319 | while True: 320 | line = fh.readline().strip() 321 | if line.startswith('nearest'): 322 | x, y, z = [ 323 | float(item) for item in line.split()[5:8]] 324 | station = AttribDict( 325 | {'station': name, 'x': x, 'y': y, 'z': z}) 326 | reciever.append(station) 327 | break 328 | 329 | if line.startswith('Geographic and Cartesian'): 330 | for i in range(4): 331 | line = fh.readline().split(',') 332 | number, lon = line[0].split(':') 333 | line[0] = lon 334 | corner = AttribDict({'number': number}) 335 | for item in line: 336 | k, v = item.split('=') 337 | corner[k.strip()] = float(v) 338 | corners.append(corner) 339 | continue 340 | 341 | if line.startswith('Start Time'): 342 | start_time, goal_time = line.split('Goal Time =') 343 | 344 | output_.start_time = float(start_time.split('=')[1]) 345 | output_.goal_time = float(goal_time) 346 | continue 347 | 348 | if line.startswith('Number of time steps'): 349 | npts, dt = line.split('dt:') 350 | 351 | output_.npts = int(npts.split('=')[1]) 352 | output_.dt = float(dt) 353 | continue 354 | 355 | if line.startswith('Total seismic moment'): 356 | output_.M0 = float(line.split()[4]) 357 | continue 358 | 359 | if line.startswith('Moment magnitude'): 360 | output_.Mw = float(line.split()[3]) 361 | else: 362 | output_ = filename 363 | super(Outputfile, self).__init__(output_) 364 | 365 | 366 | def read_metadata(inputfile, outputfile): 367 | """ 368 | Function to read both SW4 input and output files at once. 369 | """ 370 | return Inputfile(inputfile), Outputfile(outputfile) 371 | 372 | 373 | def _decode_string_value(string_item): 374 | """ 375 | Converts string representations of int/float to the 376 | corresponding Python type. 377 | 378 | Parameters 379 | ---------- 380 | string_item: str 381 | Configuration value from SW4 input file in its string 382 | representation. 383 | 384 | Returns 385 | ------- 386 | int or float or str 387 | Configuration value from SW4 input file as the correct 388 | Python type (bool values specified as ``0`` or ``1`` in SW4 389 | input file will still be of ``int`` type). 390 | """ 391 | try: 392 | return int(string_item) 393 | except ValueError: 394 | pass 395 | try: 396 | return float(string_item) 397 | except ValueError: 398 | pass 399 | return string_item 400 | 401 | 402 | def _parse_input_file_and_folder(input_file=None, folder=None): 403 | """ 404 | Helper function to unify input location (or `None`) and output 405 | folder to work on. 406 | 407 | Use cases (in order of preference): 408 | 409 | * ``input_file="/path/to/input", folder=None``: 410 | input file is used for metadata and location of output folder 411 | * ``input_file="/path/to/input", folder="/path/to/output"``: 412 | input file is used for metadata, folder location is specified 413 | separately (make sure to not mismatch). 414 | * ``input_file=None, folder="/path/to/output"``: 415 | Do not use metadata from input (station locations etc. will not 416 | show up in plots) and only use output files from specified 417 | location. 418 | """ 419 | if input_file is None and folder is None: 420 | msg = ("At least one of `input_file` or `folder` has to be " 421 | "specified.") 422 | raise ValueError(msg) 423 | 424 | if input_file: 425 | input_folder = os.path.dirname(os.path.abspath(input_file)) 426 | input_ = Inputfile(input_file) 427 | else: 428 | input_ = None 429 | 430 | if input_: 431 | folder_ = os.path.join(input_folder, input_.fileio[0].path) 432 | if folder and os.path.abspath(folder) != folder_: 433 | msg = ("Both `input` and `folder` option specified. Overriding " 434 | "folder found in input file ({}) with user specified " 435 | "folder ({}).").format(folder_, folder) 436 | warn(msg) 437 | else: 438 | folder = folder_ 439 | return input_, folder 440 | -------------------------------------------------------------------------------- /pySW4/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Various utilities for pySW4. 4 | """ 5 | from __future__ import absolute_import, print_function, division 6 | 7 | from .utils import * 8 | from .spectral import fourier_spectrum, psde 9 | from . import geo 10 | 11 | -------------------------------------------------------------------------------- /pySW4/utils/spectral.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python module for spectral analysis. 3 | 4 | .. module:: spectral 5 | 6 | :author: 7 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 8 | 9 | :copyright: 10 | Shahar Shani-Kadmiel 11 | 12 | :license: 13 | This code is distributed under the terms of the 14 | GNU Lesser General Public License, Version 3 15 | (https://www.gnu.org/copyleft/lesser.html) 16 | """ 17 | from __future__ import absolute_import, print_function, division 18 | 19 | import sys 20 | import numpy as np 21 | from matplotlib.mlab import psd 22 | import obspy 23 | 24 | 25 | def next_power_2(x): 26 | return 1 << (x - 1).bit_length() 27 | 28 | 29 | def psde(data, womean=False, winsize='default', stepsize=None, 30 | delta=None, verbose=False): 31 | """ 32 | Wrapper for :func:`matplotlib.mlab.psd` for conveniently estimating 33 | the power spectral density of a signal. 34 | 35 | See :func:`~pySW4.utils.spectral.fourier_spectrum` documentation for 36 | keyword argument explanation and rational. 37 | 38 | Returns 39 | ------- 40 | 2 :class:`~numpy.ndarray` 41 | 42 | - frequency array and 43 | - amplitude array 44 | """ 45 | 46 | # if data is a sequence 47 | if type(data) in [tuple, list, np.ndarray]: 48 | if verbose: 49 | message = 'Processing data as a sequence...' 50 | print(message) 51 | sys.stdout.flush() 52 | 53 | signal = np.array(data) 54 | if delta is None: 55 | msg = ('If data is not an `obspy.core.trace.Trace` object\n' 56 | '``delta`` must be supplied.') 57 | raise ValueError(msg) 58 | 59 | # if data is an obspy Trace object 60 | elif type(data) is obspy.core.trace.Trace: 61 | if verbose: 62 | message = ('Processing data as an `obspy.core.trace.Trace` ' 63 | 'object...') 64 | print(message) 65 | sys.stdout.flush() 66 | 67 | signal = data.data 68 | delta = data.stats.delta 69 | 70 | # pad the signal and fft all of it, no windoing... 71 | if winsize is 'default': 72 | winsize = next_power_2(signal.size) 73 | noverlap = 0 74 | if verbose: 75 | message = ('Performing PSDE on padded signal with %d points, ' 76 | 'no windoing...') 77 | print(message % winsize) 78 | sys.stdout.flush() 79 | 80 | # fft the entire signal, no windoing... 81 | elif winsize is None: 82 | winsize = signal.size 83 | noverlap = 0 84 | if verbose: 85 | message = ('Performing PSDE on entire signal with %d points, no ' 86 | 'windoing...') 87 | print(message % winsize) 88 | sys.stdout.flush() 89 | 90 | # cut the signal into chuncks 91 | else: 92 | winsize = int(winsize) 93 | if stepsize is None: 94 | stepsize = winsize 95 | 96 | noverlap = winsize - stepsize 97 | if noverlap < 0: 98 | msg = '``stepsize`` must be smaller than or equal to ``winsize``.' 99 | raise ValueError(msg) 100 | 101 | n_of_win = signal.size // (winsize - noverlap) 102 | 103 | if verbose: 104 | message = ('Performing PSDE on %d long signal with %d windows of ' 105 | 'size %d points and %d points overlap. May take a ' 106 | 'while if ``winsize`` or ``stepsize`` are small...') 107 | print(message % (signal.size, n_of_win, winsize, noverlap)) 108 | sys.stdout.flush() 109 | 110 | if womean: 111 | detrend = 'mean' 112 | else: 113 | detrend = 'none' 114 | 115 | amp, freq = psd(signal, NFFT=winsize, Fs=1 / delta, detrend=detrend, 116 | noverlap=noverlap) 117 | return freq, amp 118 | 119 | 120 | def fourier_spectrum(data, womean=False, winsize=None, stepsize=None, 121 | delta=None, verbose=False): 122 | """ 123 | Compute the Fourier spectrum of a signal either as a whole or as 124 | a series of windows with or without overlaps for smoother spectrum. 125 | 126 | The reason for windowing a signal into smaller chunks is to smooth a 127 | 'noisy' Fourier spectrum of a very long signal. The frequency 128 | resolution of the FFT is related to the number of points (npts) 129 | passed into the FFT. The number of evenly spaced frequencies, 130 | sometimes refered to as bins, resolved between 0 and the nyquist 131 | frequency, 0.5/delta are (0.5*npts)+1 if npts is even or 132 | 0.5*(npts+1) if npts is odd. 133 | 134 | So the FFT of a signal of length 2048 npts and delta of 0.01 will 135 | have a nyquist frequency of 50 Hz and 1025 (aka FFT size or bins) 136 | resolved frequencies making the lowest resolved frequency 0.0488 Hz. 137 | Had the signal been longer, say 10000 points (100 seconds), the FFT 138 | size would be 5001 and the frequency resolution would be 0.01 Hz 139 | making the the spectrum very noisy numerically with many frequencies 140 | that don't contain any real information. 141 | 142 | Pay attention to the ``stepsize`` parameter. Leave it ``None`` if 143 | you are not sure what is the correct value. If a high energy peak in 144 | the time domain is picked up by more than one window due to ovelap, 145 | that can be accumulated to affect the over all amplitude in the 146 | frequency domain. On the other hand, if by slicing the signal into 147 | windows you introduce step-functions as the windowed signal starts 148 | with a steep rise or ends with a steep fall, the resulting frequency 149 | spectrum will contain false amplitudes at high frequencies as a 150 | result. Setting an overlap will get rid of this problem as these 151 | unwanted effects will be canceled out by one another or averaged by 152 | several windows. 153 | 154 | .. note:: This function is a convinient wrapper for the 155 | :func:`~numpy.fft` function. 156 | 157 | Parameters 158 | ---------- 159 | data : :class:`~numpy.ndarray` or :class:`~obspy.core.trace.Trace` instance 160 | If a sequence (:class:`~numpy.ndarray`) of time-history values 161 | is passed ``delta`` must be supplied as well. Otherwise pass an 162 | :class:`~obspy.core.trace.Trace` instance. 163 | 164 | womean : bool 165 | Remove the mean of the signal before performing the transform. 166 | Default is False, transform as is. In cases where there is a 167 | shift in the signal such that the baseline is not zero, setting 168 | ``womean=True`` will result in a smaller DC. 169 | 170 | winsize : int 171 | By default `winsize` is None, taking the FFT of the entire 172 | signal as-is. Otherwise, `winsize` sets the size of the 173 | sliding window, taking `winsize` points of the signal at a 174 | time. Works fastest when `winsize` is a whole power of 2, 175 | i.e., 128, 512, 1024, 2048, 4096 etc. 176 | 177 | stepsize : int 178 | The number of points by which the window slides each time. 179 | By default, `stepsize` is None, making it equal to 180 | `winsize`, no overlap. Setting `stepsize` to half 181 | `winsize` is common practice and will cause a 50% overlap. 182 | 183 | delta : float 184 | The dt from one sample to the next such that 1/delta is the 185 | sampling rate. 186 | 187 | verbose : bool 188 | If set to ``True`` some information about the process is 189 | printed. 190 | 191 | Returns 192 | ------- 193 | 2 :class:`~numpy.ndarray` 194 | 195 | - frequency array and 196 | - amplitude array 197 | """ 198 | 199 | def _fft(signal, delta): 200 | freq = np.fft.rfftfreq(signal.size, delta) 201 | amp = np.abs(np.fft.rfft(signal)) * delta 202 | return freq, amp 203 | 204 | # if data is a sequence 205 | if type(data) in [tuple, list, np.ndarray]: 206 | if verbose: 207 | message = 'Processing data as a sequence...' 208 | print(message) 209 | sys.stdout.flush() 210 | 211 | signal = np.array(data) 212 | if delta is None: 213 | msg = ('If data is not an `obspy.core.trace.Trace` object\n' 214 | '``delta`` must be supplied.') 215 | raise ValueError(msg) 216 | 217 | # if data is an obspy Trace object 218 | elif type(data) is obspy.core.trace.Trace: 219 | if verbose: 220 | message = ('Processing data as an `obspy.core.trace.Trace` ' 221 | 'object...') 222 | print(message) 223 | sys.stdout.flush() 224 | 225 | signal = data.data 226 | # if womean is not False, remove the mean befor transform 227 | if womean: 228 | signal -= signal.mean() 229 | delta = data.stats.delta 230 | 231 | # fft the entire signal, no windoing... 232 | if winsize is None: 233 | if verbose: 234 | message = ('Performing FFT on entire signal with %d points, ' 235 | 'no windoing...') 236 | print(message % signal.size) 237 | sys.stdout.flush() 238 | 239 | return _fft(signal, delta) 240 | 241 | # cut the signal into overlaping windows, 242 | # fft each window and average the sum 243 | else: 244 | winsize = int(winsize) 245 | if stepsize is None: 246 | stepsize = winsize 247 | else: 248 | stepsize = int(stepsize) 249 | 250 | if stepsize > winsize: 251 | msg = '``stepsize`` must be smaller than or equal to ``winsize``.' 252 | raise ValueError(msg) 253 | if winsize > signal.size: 254 | msg = '``winsize`` must be smaller than or equal to ``npts``.' 255 | raise ValueError(msg) 256 | 257 | overlap = winsize - stepsize 258 | n_of_win = signal.size // (winsize - overlap) 259 | 260 | if verbose: 261 | message = ('Performing FFT on %d long signal with %d windows of ' 262 | 'size %d points and %d points overlap. May take a ' 263 | 'while if ``winsize`` or ``stepsize`` are small...') 264 | print(message % (signal.size, n_of_win, winsize, overlap)) 265 | sys.stdout.flush() 266 | 267 | # pad the end of the signal with zeros 268 | # to make sure the entire signal is used 269 | # for the fft. 270 | padded = np.pad(signal, (0, winsize), mode='constant') 271 | freq = np.fft.rfftfreq(winsize, delta) 272 | amps = np.zeros_like(freq) 273 | 274 | for i in xrange(n_of_win): 275 | if verbose: 276 | print('\rFFT window %d out of %d...' % (i + 1, n_of_win)), 277 | sys.stdout.flush() 278 | 279 | start = i * stepsize 280 | stop = start + winsize 281 | amps += np.abs(np.fft.rfft(padded[start:stop])) * delta 282 | 283 | amp = amps / n_of_win 284 | return freq, amp 285 | -------------------------------------------------------------------------------- /pySW4/utils/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Python module with general utilities. 4 | 5 | .. module:: utils 6 | 7 | :author: 8 | Shahar Shani-Kadmiel (s.shanikadmiel@tudelft.nl) 9 | 10 | :copyright: 11 | Shahar Shani-Kadmiel 12 | 13 | :license: 14 | This code is distributed under the terms of the 15 | GNU Lesser General Public License, Version 3 16 | (https://www.gnu.org/copyleft/lesser.html) 17 | """ 18 | from __future__ import absolute_import, print_function, division 19 | 20 | import sys 21 | from warnings import warn 22 | from scipy.interpolate import griddata 23 | import numpy as np 24 | from pyproj import Proj, transform 25 | try: 26 | import cv2 27 | except ImportError: 28 | warn("OpenCV not found. Don't worry about this unless you want to use " 29 | "`pySW4.utils.resample` in `draft` mode.") 30 | from matplotlib.colors import LinearSegmentedColormap, ColorConverter 31 | 32 | 33 | def resample(data, extent, shape=None, method='linear', 34 | draft=False, corners=None, origin='nw', verbose=False): 35 | """ 36 | Resample or interpolate an array on to a grid with new extent and or 37 | new shape. 38 | 39 | Parameters 40 | ---------- 41 | data : 3-tuple or :class:`~numpy.ndarray` 42 | Either a tuple of 3x2d :class:`~numpy.ndarray` s ``(X,Y,Z)`` or 43 | a single 2d :class:`~numpy.ndarray` ``Z``. If a ``draft=True`` 44 | and a single ``Z`` array is passed, ``corners`` must be 45 | supplied. 46 | 47 | extent : tuple or list 48 | Extent of the output grid to which the array is interpolated. 49 | 50 | shape : tuple 51 | The shape of the output grid, defaults to the shape of the input 52 | array. If no shape is passed, output grid is the same shape as 53 | input array. 54 | 55 | method : str 56 | Method of interpolation. One of 57 | 58 | - ``nearest`` - return the value at the data point closest to 59 | the point of interpolation. This is the faster of the three. 60 | - ``linear`` - tesselate the input point set to n-dimensional 61 | simplices, and interpolate linearly on each simplex. 62 | - ``cubic`` - return the value determined from a piecewise 63 | cubic, continuously differentiable (C1), and approximately 64 | curvature-minimizing polynomial surface. 65 | 66 | draft : bool 67 | If set to True, a very fast OpenCV algorithem is used to 68 | interpolate the array by reprojection the data corners onto 69 | arbitrary corners. See `Stack Overflow post 70 | `_ for explanation. 71 | 72 | corners : 4-tuple 73 | Points to which the corners of ``Z`` will be tranformed to. 74 | After transformation, the TL, TR, BR, BL corners of the ``Z`` 75 | array will be transformed to the coordinates in: 76 | 77 | ``corners=(top-left, top-right, bottom-right, bottom-left)`` 78 | 79 | where each point is a tuple of ``(lon, lat)``. 80 | Optionally, corners can be a ``(src, dst)`` tuple, where 81 | ``src``, ``dst`` are each a tuple of ``n`` points. Each point in 82 | ``src`` is transformed to the respective point in extent 83 | coordinates in ``dst``. 84 | 85 | origin : str 86 | Corresponds to ``Z[0, 0]`` of the array or grid, 87 | either ``'nw'`` (default), or ``'sw'``. 88 | 89 | verbose : bool 90 | If set to True, some information about the transformation is 91 | provided. 92 | 93 | Returns 94 | ------- 95 | 3x2d :class:`~numpy.ndarray` s or 1x2d :class:`~numpy.ndarray` 96 | If ``data`` is a tuple of 3x2d arrays ``(X,Y,Z)`` and ``draft`` 97 | is ``False``, ``xi, yi, zi`` are returned. If ``draft`` is 98 | ``True``, only ``zi`` is returned. 99 | """ 100 | if len(data) == 3: 101 | X, Y, Z = data 102 | elif data.ndim == 2: 103 | X, Y, Z = None, None, data 104 | else: 105 | print('Error: data must be a tuple of 3x2d ' 106 | ':class:`numpy.ndarry` (X, Y, Z) or one 2d ``Z`` array') 107 | 108 | w, e, s, n = extent 109 | 110 | # if no output shape is supplied 111 | # use the shape of the input array Z 112 | if not shape: 113 | shape = Z.shape 114 | z_ny, z_nx = Z.shape 115 | ny, nx = shape 116 | 117 | if X is None and Y is None: 118 | X, Y = np.meshgrid(np.linspace(w, e, z_nx), 119 | np.linspace(s, n, z_ny)) 120 | 121 | if draft is False: # the long and accurate way... 122 | if verbose: 123 | message = ('Accurately interpolating data onto a grid of ' 124 | 'shape %dx%d and extent %.2f,%.2f,%.2f,%.2f ' 125 | 'using X, Y, Z arrays.\n' 126 | ' This may take a while...') 127 | print(message.format(ny, nx, w, e, s, n)) 128 | sys.stdout.flush() 129 | 130 | xi, yi = np.meshgrid(np.linspace(w, e, nx), 131 | np.linspace(s, n, ny)) 132 | zi = griddata((X.ravel(), Y.ravel()), Z.ravel(), (xi, yi), 133 | method=method) 134 | return xi, yi, zi 135 | 136 | elif draft is True: # the fast and less accurate way... 137 | try: # both src and dst are passed 138 | src, dst = corners 139 | src, dst = tuple(src), tuple(dst) 140 | 141 | except ValueError: # only dst corners passed 142 | src = ((0, 0), (nx, 0), (nx, ny), (0, ny)) 143 | dst = corners 144 | 145 | xc, yc = [p[0] for p in dst], [p[1] for p in dst] 146 | xc, yc = xy2pixel_coordinates(xc, yc, extent, shape, origin) 147 | dst = tuple(zip(xc, yc)) 148 | 149 | if verbose: 150 | message = ('Transforming points:\n' 151 | '%s\n' 152 | 'in the data to points:\n' 153 | '%s\n' 154 | 'in the the output grid of shape %dx%d and ' 155 | 'extent %.2f,%.2f,%.2f,%.2f.') 156 | print(message.format(src, dst, nx, ny, w, e, s, n)) 157 | sys.stdout.flush() 158 | 159 | # Compute the transformation matrix which places 160 | # the corners Z at the corners points bounding the 161 | # data in output grid pixel coordinates 162 | tranformation_matrix = cv2.getPerspectiveTransform(np.float32(src), 163 | np.float32(dst)) 164 | 165 | # Make the transformation 166 | interpolation = {'nearest' : 0, 167 | 'linear' : 1, 168 | 'cubic' : 2} 169 | zi = cv2.warpPerspective(Z, tranformation_matrix, 170 | (nx, ny), 171 | flags=interpolation[method], 172 | borderMode=0, 173 | borderValue=np.nan) 174 | 175 | return zi 176 | 177 | 178 | def make_cmap(colors, position=None, bit=False, named=True): 179 | """ 180 | Make a color map compatible with matplotlib plotting functions. 181 | 182 | Parameters 183 | ---------- 184 | colors : list 185 | List of `matplotlib named colors 186 | `_. 187 | Arrange your colors so that the first color is the lowest value 188 | for the colorbar and the last is the highest. 189 | 190 | position : list 191 | List of values from 0 to 1, controls the position of each color 192 | on the cmap. If None (default), an evenly spaced cmap is 193 | created. 194 | 195 | **Tip:** 196 | You can construct the positions sequence in data units and 197 | then normalize so that the forst value is 0 and the last 198 | value is 1. 199 | 200 | named : bool 201 | If set to False, colors are regarded as tuples of RGB values. 202 | The RGB values may either be in 8-bit [0 to 255], in which case 203 | ``bit`` must be set to True or arithmetic [0 to 1] (default). 204 | 205 | Returns 206 | ------- 207 | :class:`~matplotlib.colors.LinearSegmentedColormap` 208 | An instance of the 209 | :class:`~matplotlib.colors.LinearSegmentedColormap` cmap class. 210 | """ 211 | bit_rgb = np.linspace(0, 1, 256) 212 | if position is None: 213 | position = np.linspace(0, 1, len(colors)) 214 | else: 215 | if len(position) != len(colors): 216 | raise ValueError('Position length must be the same as colors\n') 217 | elif position[0] != 0 or position[-1] != 1: 218 | raise ValueError('Position must start with 0 and end with 1\n') 219 | if bit: 220 | for i in range(len(colors)): 221 | colors[i] = (bit_rgb[colors[i][0]], 222 | bit_rgb[colors[i][1]], 223 | bit_rgb[colors[i][2]]) 224 | cdict = {'red' : [], 225 | 'green' : [], 226 | 'blue' : []} 227 | if named: 228 | colors = [ColorConverter().to_rgb(c) for c in colors] 229 | for pos, color in zip(position, colors): 230 | cdict['red'].append((pos, color[0], color[0])) 231 | cdict['green'].append((pos, color[1], color[1])) 232 | cdict['blue'].append((pos, color[2], color[2])) 233 | 234 | cmap = LinearSegmentedColormap('my_colormap', cdict, 256) 235 | return cmap 236 | 237 | 238 | def trim_cmap(cmap, cmin, cmax, n=256): 239 | """ 240 | Trim a `cmap` to `cmin` and `cmax` limits in the range 0 to 1. 241 | Use :class:`~matplotlib.colors.Normalize` with `vmin`, `vmax` of 242 | the plot or data to get `cmin` and `cmax` 243 | 244 | Examples 245 | -------- 246 | >>> norm = Normalize(-5, 10) 247 | >>> norm(-2) 248 | 0.20000000000000001 249 | >>> norm(6) 250 | 0.73333333333333328 251 | 252 | """ 253 | cmap_ = LinearSegmentedColormap.from_list( 254 | 'trim({},{:f},{:f})'.format(cmap.name, cmin, cmax), 255 | cmap(np.linspace(cmin, cmax, n))) 256 | return cmap_ 257 | 258 | 259 | def flatten(ndarray): 260 | """ 261 | Returns a flattened 1Darray from any multidimentional array. This 262 | function is recursive and may take a long time on large data. 263 | """ 264 | for item in ndarray: 265 | try: 266 | for subitem in flatten(item): 267 | yield subitem 268 | except TypeError: 269 | yield item 270 | 271 | 272 | def close_polygon(x, y): 273 | """ 274 | Return a list of points with the first point added to the end. 275 | """ 276 | return list(x) + [x[0]], list(y) + [y[0]] 277 | 278 | 279 | def get_corners(x, y): 280 | """ 281 | Extract the corners of the data from its coordinates arrays 282 | `x` and `y`. 283 | 284 | Returns 285 | ------- 286 | 2x list 287 | - one list of longitudes (or `x`) and 288 | - another list of latitudes (or `y`). 289 | """ 290 | xc = [x[0, 0], x[0, -1], x[-1, -1], x[-1, 0]] 291 | yc = [y[0, 0], y[0, -1], y[-1, -1], y[-1, 0]] 292 | 293 | return xc, yc 294 | 295 | 296 | def line_in_loglog(x, m, b): 297 | """ 298 | Function to calculate the :math:`y` values of a line in log-log 299 | space according to 300 | 301 | .. math:: y(x) = b \cdot 10^{\log_{10} x^m}, 302 | 303 | where: 304 | 305 | - :math:`m` is the slope of the line, 306 | - :math:`b` is the :math:`y(x=1)` value and 307 | - :math:`x` is a sequence of values on the x-axis. 308 | """ 309 | return b * 10**(np.log10(x**m)) 310 | 311 | 312 | def xy2pixel_coordinates(x, y, extent, shape, origin='nw'): 313 | """ 314 | Get the pixel coordinates on a grid. 315 | 316 | Parameters 317 | ---------- 318 | x : float or sequence 319 | `x` coordianates (might also be longitudes). 320 | 321 | y : float or sequence 322 | `y` coordianates (might also be latitudes). 323 | 324 | extent : tuple or list 325 | Extent of the output grid to which the array is interpolated. 326 | 327 | shape : tuple 328 | The shape of the output grid, defaults to the shape of the input 329 | array. If no shape is passed, output grid is the same shape as 330 | input array. 331 | 332 | origin : str 333 | Corresponds to ``Z[0, 0]`` of the array or grid, 334 | either ``'nw'`` (default), or ``'sw'``. 335 | 336 | Returns 337 | ------- 338 | 2x list 339 | - one list of longitudes (or ``x``) and 340 | - another list of latitudes (or ``y``). 341 | """ 342 | x, y = np.array([x, y]) 343 | w, e, s, n = extent 344 | ny, nx = shape 345 | dx = float(e - w) / nx 346 | dy = float(n - s) / ny 347 | 348 | if origin is 'nw': 349 | xc = (x - w) / dx 350 | yc = (n - y) / dy 351 | elif origin is 'sw': 352 | xc = (x - w) / dx 353 | yc = (y - s) / dy 354 | else: 355 | raise ValueError("Origin must be 'nw' or 'sw'.") 356 | 357 | return xc, yc 358 | 359 | 360 | def geographic2cartesian(proj4=None, 361 | sw4_default_proj='+proj=latlong +datum=NAD83', 362 | lon0=0, lat0=0, az=0, 363 | m_per_lat=111319.5, m_per_lon=None, 364 | lon=None, lat=None, x=None, y=None): 365 | """ 366 | Calculate SW4 cartesian coordinats from geographic coordinates 367 | `lon`, `lat` in the `proj4` projection. If `x`, `y` 368 | coordinates are given (`x` and `y` are not ``None``) the 369 | inverse transformation is performed. 370 | 371 | Parameters 372 | ---------- 373 | proj4 : str or dict or None 374 | EPSG or Proj4. If EPSG then string should be 'epsg:number' 375 | where number is the EPSG code. See the `Geodetic Parameter 376 | Dataset Registry `_ for more 377 | information. If Proj4 then either a dictionary: 378 | :: 379 | 380 | {'proj': projection, 'datum': 'datum', 'units': units, 381 | 'lon_0': lon0, 'lat_0': lat0, 'scale': scale ... etc.} 382 | 383 | or a string: 384 | :: 385 | 386 | '+proj=projection +datum=datum +units=units +lon_0=lon0 \\ 387 | +lat_0=lat0 +scale=scale ... etc.' 388 | 389 | should be given. 390 | 391 | See the `Proj4 `_ 392 | documentation for a list of general Proj4 parameters. 393 | 394 | In the context of SW4 this dictionary or string should be taken 395 | from the ``grid`` line in the SW4 input file. 396 | 397 | sw4_default_proj : str 398 | SW4 uses '+proj=latlong +datum=NAD83' as the default projection. 399 | Pass a different projection for other purposes. See ``proj4``. 400 | 401 | lon0 : float 402 | Longitude of the grid origin in degrees. 403 | 404 | This is the ``lon`` keyword on the ``grid`` line in the SW4 405 | input file. 406 | 407 | lat0 : float 408 | Latitude of the grid origin in degrees. 409 | 410 | This is the ``lat`` keyword on the ``grid`` line in the SW4 411 | input file. 412 | 413 | az : float 414 | Azimuth between North and the ``x`` axis of the SW4 grid. 415 | 416 | This is the ``az`` keyword on the ``grid`` line in the SW4 input 417 | file. 418 | 419 | m_per_lat : float 420 | How many m to a degree latitude. (default: 111319.5 m) 421 | 422 | This is the ``mlat`` keyword on the ``grid`` line in the SW4 423 | input file. 424 | 425 | m_per_lon : float 426 | How many m to a degree longitude. If ``None``, ``m_per_lat`` is 427 | used). 428 | 429 | This is the ``mlon`` keyword on the ``grid`` line in the SW4 430 | input file. 431 | 432 | lon : float 433 | Longitude coordinates in degrees to convert (geo. ===> cart.). 434 | 435 | lat : float 436 | Latitude coordinates in degrees to convert (geo. ===> cart.). 437 | 438 | x : float 439 | ``x`` coordinates in meters to convert (cart. ===> geo.). 440 | 441 | y : float 442 | ``y`` coordinates in meters to convert (cart. ===> geo.). 443 | 444 | Returns 445 | ------- 446 | Converted coordinates: 447 | - x, y if geo. ===> cart. was invoked or 448 | - lon, lat if cart. ===> geo. was invoked. 449 | 450 | :: 451 | 452 | X 453 | ⋰ 454 | ⋰ 455 | o------->Y 456 | | 457 | | 458 | V 459 | Z 460 | 461 | If the azimuth of the SW4 grid is ``0`` this translates to: 462 | 463 | - X == Northing 464 | - Y == Easting 465 | - Z == Vertical (inverted!) 466 | 467 | """ 468 | 469 | # No Proj4 projection, use simple transformations 470 | if not proj4 and lon is not None and lat is not None: 471 | x_out, y_out = simple_lonlat2xy(lon, lat, lon0, lat0, az, 472 | m_per_lat, m_per_lon) 473 | elif not proj4 and x is not None and y is not None: 474 | x_out, y_out = simple_xy2lonlat(x, y, lon0, lat0, az, 475 | m_per_lat, m_per_lon) 476 | 477 | # Proj4 projection is used, follow these tranformations 478 | else: 479 | # Proj4 480 | try: 481 | geo_proj = Proj(init=proj4) 482 | except RuntimeError: 483 | geo_proj = Proj(proj4) 484 | 485 | # sw4 projection (either the default or a user's choice) 486 | try: 487 | sw4_proj = Proj(init=sw4_default_proj) 488 | except RuntimeError: 489 | sw4_proj = Proj(sw4_default_proj) 490 | 491 | xoffset, yoffset = transform(sw4_proj, geo_proj, lon0, lat0) 492 | 493 | # lon, lat ===> x, y 494 | if lon is not None and lat is not None: 495 | x_, y_ = transform(sw4_proj, geo_proj, lon, lat) 496 | x_ -= xoffset 497 | y_ -= yoffset 498 | 499 | az_ = np.radians(az) 500 | x_out = x_ * np.sin(az_) + y_ * np.cos(az_) 501 | y_out = x_ * np.cos(az_) - y_ * np.sin(az_) 502 | 503 | # x, y ===> lon, lat 504 | elif x is not None and y is not None: 505 | az_ = np.radians(az) 506 | x_ = x * np.sin(az_) + y * np.cos(az_) + xoffset 507 | y_ = x * np.cos(az_) - y * np.sin(az_) + yoffset 508 | 509 | x_out, y_out = transform(geo_proj, sw4_proj, x_, y_) 510 | 511 | return x_out, y_out 512 | 513 | 514 | def simple_xy2lonlat(x, y, lon0=0, lat0=0, az=0, 515 | m_per_lat=111319.5, m_per_lon=None): 516 | """ 517 | Project cartesian `x, y` coordinates to geographical `lat, lon`. 518 | 519 | This transformation is based on the default projection used by 520 | SW4. See section *3.1.1* of the `SW4 521 | `_ User Guide for more 522 | details. 523 | 524 | :: 525 | 526 | X 527 | ⋰ 528 | ⋰ 529 | o------->Y 530 | | 531 | | 532 | V 533 | Z 534 | 535 | If the azimuth of the SW4 grid is ``0`` this translates to: 536 | 537 | - X == Northing 538 | - Y == Easting 539 | - Z == Vertical (inverted!) 540 | 541 | 542 | Parameters 543 | ---------- 544 | x : float or sequence 545 | `x` coordianates. 546 | 547 | y : float or sequence 548 | `y` coordianates. 549 | 550 | origin : 2-tuple or list 551 | `lat, lon` coordinates of the bottom-right corner of the 552 | `x, y` grid. 553 | 554 | az : float 555 | Rotation of the grid aroud the vertical (z-axis). See the 556 | SW4 User Guide for more information. 557 | 558 | m_per_lat : float 559 | How many m to a degree latitude. (default: 111319.5 m) 560 | 561 | m_per_lon : float 562 | How many m to a degree longitude. If ``None``, `m_per_lat` is 563 | used). 564 | 565 | Returns 566 | -------- 567 | 2x :class:`~numpy.ndarray` 568 | Two arrays with `lon` and `lat` projected points. 569 | """ 570 | 571 | az_ = np.radians(az) 572 | lat = lat0 + (x * np.cos(az_) - y * np.sin(az_)) / m_per_lat 573 | 574 | if m_per_lon: 575 | lon = lon0 + (x * np.sin(az_) + y * np.cos(az_)) / m_per_lon 576 | else: 577 | lon = (lon0 + (x * np.sin(az_) + y * np.cos(az_)) / 578 | (m_per_lat * np.cos(np.radians(lat)))) 579 | 580 | return lon, lat 581 | 582 | 583 | def simple_lonlat2xy(lon, lat, lon0=0, lat0=0, az=0, 584 | m_per_lat=111319.5, m_per_lon=None): 585 | """ 586 | Project geographical `lat, lon` to cartesian `x, y` coordinates. 587 | 588 | This is the inverse function of :func:`~pySW4.utils.utils.xy2latlon` 589 | function. 590 | 591 | Parameters 592 | ----------- 593 | lon : float or sequence 594 | `lon` coordianates. 595 | 596 | lat : float or sequence 597 | `lat` coordianates. 598 | 599 | origin : 2-tuple or list 600 | `lat, lon` coordinates of the bottom-right corner of the 601 | `x, y` grid. 602 | 603 | az : float 604 | Rotation of the grid aroud the vertical (z-axis). See the 605 | SW4 User Guide for more information. 606 | 607 | m_per_lat : float 608 | How many m to a degree latitude. (default: 111319.5 m) 609 | 610 | m_per_lon : float 611 | How many m to a degree longitude. If ``None``, `m_per_lat` is 612 | used). 613 | 614 | Returns 615 | -------- 616 | 2x :class:`~numpy.ndarray` 617 | Two arrays with ``x`` and ``y`` projected points. 618 | """ 619 | 620 | az_ = np.radians(az) 621 | if m_per_lon: 622 | x = (m_per_lat * np.cos(az_) * (lat - lat0) 623 | + m_per_lon * (lon - lon0) * np.sin(az_)) 624 | y = (m_per_lat * -np.sin(az_) * (lat - lat0) 625 | + m_per_lon * (lon - lon0) * np.cos(az_)) 626 | else: 627 | x = (m_per_lat * (np.cos(az_) * (lat - lat0) 628 | + (np.cos(np.radians(lat)) * (lon - lon0) 629 | * np.sin(az_)))) 630 | y = (m_per_lat * (-np.sin(az_) * (lat - lat0) 631 | + np.cos(np.radians(lat)) * (lon - lon0) 632 | * np.cos(az_))) 633 | 634 | return x, y 635 | 636 | 637 | def _list(x): 638 | try: 639 | return list(x) 640 | except TypeError: 641 | return [x] 642 | 643 | 644 | def nearest_values(array, value, threshold, retvalue=False): 645 | """ 646 | Get the indices (or elements) of an array with a threshold range 647 | around a central value. 648 | 649 | If `retvalue` is true, the elements of the array are returned, 650 | otherwise a True or False array of the indices of these elements 651 | is returned. 652 | 653 | Examples 654 | -------- 655 | >>> array = np.random.randint(-10, 10, size=10) 656 | >>> print(array) 657 | [ 3 -4 9 6 8 -1 -4 -7 -6 7] 658 | >>> 659 | >>> idx = nearest_values(array, 5, 2) 660 | >>> print(idx) 661 | >>> print(nearest_values(array, 5, 2, retvalue=True)) 662 | >>> print array[idx] 663 | [ True False False True False False False False False True] 664 | [3 6 7] 665 | [3 6 7] 666 | """ 667 | array = np.array(array) # make sure aray is a numpy array 668 | left = value - threshold 669 | right = value + threshold 670 | idx = (array >= left) * (array <= right) 671 | if retvalue: 672 | return array[idx] 673 | return idx 674 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import os 3 | import re 4 | from setuptools import setup 5 | 6 | INSTALL_REQUIRES = [ 7 | 'numpy', 8 | 'scipy', 9 | 'matplotlib', 10 | 'obspy', 11 | 'gdal', 12 | ] 13 | 14 | SETUP_DIRECTORY = os.path.dirname(os.path.abspath(inspect.getfile( 15 | inspect.currentframe()))) 16 | ENTRY_POINTS = { 17 | 'console_scripts': [ 18 | 'pySW4-plot-image = pySW4.cli.plot_image:main', 19 | 'pySW4-create-plots = pySW4.cli.create_all_plots:main', 20 | 'png2mp4 = pySW4.cli.png2mp4:main']} 21 | 22 | 23 | def find_packages(): 24 | """ 25 | Simple function to find all modules under the current folder. 26 | """ 27 | modules = [] 28 | for dirpath, _, filenames in os.walk( 29 | os.path.join(SETUP_DIRECTORY, "pySW4")): 30 | if "__init__.py" in filenames: 31 | modules.append(os.path.relpath(dirpath, SETUP_DIRECTORY)) 32 | return [_i.replace(os.sep, ".") for _i in modules] 33 | 34 | # get the package version from from the main __init__ file. 35 | version_regex_pattern = r"__version__ += +(['\"])([^\1]+)\1" 36 | for line in open(os.path.join(SETUP_DIRECTORY, 'pySW4', '__init__.py')): 37 | if '__version__' in line: 38 | package_version = re.match(version_regex_pattern, line).group(2) 39 | 40 | setup( 41 | name="pySW4", 42 | version=package_version, 43 | description="Python routines for interaction with SW4", 44 | author="Shahar Shani-Kadmiel, Omry Volk, Tobias Megies", 45 | author_email="s.shanikadmiel@tudelft.nl", 46 | url="https://github.com/shaharkadmiel/pySW4", 47 | download_url="https://github.com/shaharkadmiel/pySW4.git", 48 | install_requires=INSTALL_REQUIRES, 49 | keywords=["pySW4", "seismology", "SW4"], 50 | packages=find_packages(), 51 | entry_points=ENTRY_POINTS, 52 | classifiers=[ 53 | "Programming Language :: Python", 54 | "Development Status :: 4 - Beta", 55 | "Intended Audience :: Science/Research", 56 | "Intended Audience :: Developers", 57 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 58 | "Operating System :: OS Independent", 59 | "Topic :: Software Development :: Libraries :: Python Modules", 60 | ], 61 | long_description='pySW4 is an open-source project dedicated to ' 62 | 'provide a Python framework for working with ' 63 | 'numerical simulations of seismic-wave propagation ' 64 | 'with SW4 in all phases of the task (preprocessing, ' 65 | 'post-processing and runtime visualization).' 66 | ) 67 | -------------------------------------------------------------------------------- /tutorials/berkeley.sw4: -------------------------------------------------------------------------------- 1 | # SW4 input file for testing the rfile command 2 | 3 | fileio path=./results pfs=1 verbose=2 printcycle=1 4 | 5 | # grid size is set in the grid command 6 | # DO NOT CHANGE AZ! 7 | grid x=12e3 y=11e3 z=5e3 h=20.0 lat=37.93 lon=-122.25 az=143.6380001671 proj=tmerc datum=NAD83 lon_p=-123.0 lat_p=35.0 scale=0.9996 8 | 9 | # curvilinear grid extends to z=6000 and gets approximately the same 10 | # gridsize as the finest Cartesian 11 | topography input=rfile zmax=2e3 order=3 file=./berkeley.rfile 12 | 13 | time t=90 14 | 15 | # Verison 8.3 of USGS model in rfile format 16 | # CHECK PATH TO RFILE 17 | rfile filename=berkeley.rfile directory=./ 18 | 19 | # threshold on vp and vs 20 | globalmaterial vsmin=500 vpmin=768 21 | 22 | # simple source (irrelevant for this test) 23 | source x=2.5e3 y=3.8e3 depth=3.5e3 strike=142.1 rake=180 dip=95.6 m0=1.7162e+17 t0=2 freq=7.62985 type=Liu 24 | 25 | # images 26 | # topography (can't be written without the topography command enabled) 27 | image mode=topo z=0.0 cycle=0 file=berkeley 28 | 29 | image mode=lat z=0.0 cycle=0 file=berkeley 30 | image mode=lon z=0.0 cycle=0 file=berkeley 31 | 32 | # Output images of the elastic model on the surface (z=0) 33 | image mode=rho z=0.0 cycle=0 file=berkeley 34 | image mode=p z=0.0 cycle=0 file=berkeley 35 | image mode=s z=0.0 cycle=0 file=berkeley 36 | 37 | # vertical cross section 38 | image mode=rho x=2.5e3 cycle=0 file=berkeley 39 | image mode=p x=2.5e3 cycle=0 file=berkeley 40 | image mode=s x=2.5e3 cycle=0 file=berkeley 41 | 42 | image mode=p y=2e3 cycle=0 file=berkeley 43 | image mode=p y=3.8e3 cycle=0 file=berkeley 44 | 45 | # solution on the surface 46 | image mode=magdudt z=0.0 timeInterval=5 file=berkeley 47 | image mode=hmaxdudt z=0.0 timeInterval=5 file=berkeley 48 | 49 | image mode=magdudt x=2.5e3 timeInterval=5 file=berkeley 50 | image mode=magdudt y=3.8e3 timeInterval=5 file=berkeley 51 | 52 | # stations at locations: 53 | rec lon=-122.2356 lat=37.8762 depth=0.0000 file=BDSN.BKS writeEvery=100 nsew=0 variables=velocity 54 | rec lon=-122.2543 lat=37.8749 depth=0.0000 file=BDSN.BL67 writeEvery=100 nsew=0 variables=velocity 55 | rec lon=-122.2543 lat=37.8772 depth=0.0000 file=BDSN.BL88 writeEvery=100 nsew=0 variables=velocity 56 | rec lon=-122.2610 lat=37.8735 depth=0.0000 file=BDSN.BRK writeEvery=100 nsew=0 variables=velocity 57 | rec lon=-122.2489 lat=37.8775 depth=0.0000 file=BDSN.VAK writeEvery=100 nsew=0 variables=velocity 58 | 59 | # 7x6 array of seismograms added: surface 60 | rec x=0 y=0 depth=0 file=surface_x=0_y=0_z=0_ writeEvery=100 nsew=0 variables=velocity 61 | rec x=2000 y=0 depth=0 file=surface_x=2000_y=0_z=0_ writeEvery=100 nsew=0 variables=velocity 62 | rec x=4000 y=0 depth=0 file=surface_x=4000_y=0_z=0_ writeEvery=100 nsew=0 variables=velocity 63 | rec x=6000 y=0 depth=0 file=surface_x=6000_y=0_z=0_ writeEvery=100 nsew=0 variables=velocity 64 | rec x=8000 y=0 depth=0 file=surface_x=8000_y=0_z=0_ writeEvery=100 nsew=0 variables=velocity 65 | rec x=10000 y=0 depth=0 file=surface_x=10000_y=0_z=0_ writeEvery=100 nsew=0 variables=velocity 66 | rec x=12000 y=0 depth=0 file=surface_x=12000_y=0_z=0_ writeEvery=100 nsew=0 variables=velocity 67 | rec x=0 y=2200 depth=0 file=surface_x=0_y=2200_z=0_ writeEvery=100 nsew=0 variables=velocity 68 | rec x=2000 y=2200 depth=0 file=surface_x=2000_y=2200_z=0_ writeEvery=100 nsew=0 variables=velocity 69 | rec x=4000 y=2200 depth=0 file=surface_x=4000_y=2200_z=0_ writeEvery=100 nsew=0 variables=velocity 70 | rec x=6000 y=2200 depth=0 file=surface_x=6000_y=2200_z=0_ writeEvery=100 nsew=0 variables=velocity 71 | rec x=8000 y=2200 depth=0 file=surface_x=8000_y=2200_z=0_ writeEvery=100 nsew=0 variables=velocity 72 | rec x=10000 y=2200 depth=0 file=surface_x=10000_y=2200_z=0_ writeEvery=100 nsew=0 variables=velocity 73 | rec x=12000 y=2200 depth=0 file=surface_x=12000_y=2200_z=0_ writeEvery=100 nsew=0 variables=velocity 74 | rec x=0 y=4400 depth=0 file=surface_x=0_y=4400_z=0_ writeEvery=100 nsew=0 variables=velocity 75 | rec x=2000 y=4400 depth=0 file=surface_x=2000_y=4400_z=0_ writeEvery=100 nsew=0 variables=velocity 76 | rec x=4000 y=4400 depth=0 file=surface_x=4000_y=4400_z=0_ writeEvery=100 nsew=0 variables=velocity 77 | rec x=6000 y=4400 depth=0 file=surface_x=6000_y=4400_z=0_ writeEvery=100 nsew=0 variables=velocity 78 | rec x=8000 y=4400 depth=0 file=surface_x=8000_y=4400_z=0_ writeEvery=100 nsew=0 variables=velocity 79 | rec x=10000 y=4400 depth=0 file=surface_x=10000_y=4400_z=0_ writeEvery=100 nsew=0 variables=velocity 80 | rec x=12000 y=4400 depth=0 file=surface_x=12000_y=4400_z=0_ writeEvery=100 nsew=0 variables=velocity 81 | rec x=0 y=6600 depth=0 file=surface_x=0_y=6600_z=0_ writeEvery=100 nsew=0 variables=velocity 82 | rec x=2000 y=6600 depth=0 file=surface_x=2000_y=6600_z=0_ writeEvery=100 nsew=0 variables=velocity 83 | rec x=4000 y=6600 depth=0 file=surface_x=4000_y=6600_z=0_ writeEvery=100 nsew=0 variables=velocity 84 | rec x=6000 y=6600 depth=0 file=surface_x=6000_y=6600_z=0_ writeEvery=100 nsew=0 variables=velocity 85 | rec x=8000 y=6600 depth=0 file=surface_x=8000_y=6600_z=0_ writeEvery=100 nsew=0 variables=velocity 86 | rec x=10000 y=6600 depth=0 file=surface_x=10000_y=6600_z=0_ writeEvery=100 nsew=0 variables=velocity 87 | rec x=12000 y=6600 depth=0 file=surface_x=12000_y=6600_z=0_ writeEvery=100 nsew=0 variables=velocity 88 | rec x=0 y=8800 depth=0 file=surface_x=0_y=8800_z=0_ writeEvery=100 nsew=0 variables=velocity 89 | rec x=2000 y=8800 depth=0 file=surface_x=2000_y=8800_z=0_ writeEvery=100 nsew=0 variables=velocity 90 | rec x=4000 y=8800 depth=0 file=surface_x=4000_y=8800_z=0_ writeEvery=100 nsew=0 variables=velocity 91 | rec x=6000 y=8800 depth=0 file=surface_x=6000_y=8800_z=0_ writeEvery=100 nsew=0 variables=velocity 92 | rec x=8000 y=8800 depth=0 file=surface_x=8000_y=8800_z=0_ writeEvery=100 nsew=0 variables=velocity 93 | rec x=10000 y=8800 depth=0 file=surface_x=10000_y=8800_z=0_ writeEvery=100 nsew=0 variables=velocity 94 | rec x=12000 y=8800 depth=0 file=surface_x=12000_y=8800_z=0_ writeEvery=100 nsew=0 variables=velocity 95 | rec x=0 y=11000 depth=0 file=surface_x=0_y=11000_z=0_ writeEvery=100 nsew=0 variables=velocity 96 | rec x=2000 y=11000 depth=0 file=surface_x=2000_y=11000_z=0_ writeEvery=100 nsew=0 variables=velocity 97 | rec x=4000 y=11000 depth=0 file=surface_x=4000_y=11000_z=0_ writeEvery=100 nsew=0 variables=velocity 98 | rec x=6000 y=11000 depth=0 file=surface_x=6000_y=11000_z=0_ writeEvery=100 nsew=0 variables=velocity 99 | rec x=8000 y=11000 depth=0 file=surface_x=8000_y=11000_z=0_ writeEvery=100 nsew=0 variables=velocity 100 | rec x=10000 y=11000 depth=0 file=surface_x=10000_y=11000_z=0_ writeEvery=100 nsew=0 variables=velocity 101 | rec x=12000 y=11000 depth=0 file=surface_x=12000_y=11000_z=0_ writeEvery=100 nsew=0 variables=velocity 102 | --------------------------------------------------------------------------------