Source code for pyrocko.apps.fomosto

# http://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------

'''
Fomosto - Green's function database management.
'''

import sys
import re
import os
import os.path as op
import logging
import copy
import shutil
import argparse

from pyrocko import util, trace, gf, io, fomosto
from pyrocko.gui.snuffler import marker
from pyrocko.util import mpl_show
from pyrocko.cli import PyrockoArgumentParser, dq, ldq

logger = logging.getLogger('pyrocko.apps.fomosto')
km = 1e3

program_name = 'fomosto'

description = '''
Fomosto - Green's function database management.

This is ```fomosto```, the Pyrocko tool to create, build, inspect and manage
Green's function (GF) stores. Fomosto does not compute Green's functions
itself, it provides a unified interface to configure and run several
modelling codes.

This tool's functionality is available through several subcommands. Run
```fomosto SUBCOMMAND --help``` to get further help.
'''

epilog = '''


----

Manual: https://pyrocko.org/docs/current/apps/fomosto

Tutorial: https://pyrocko.org/docs/current/apps/fomosto/tutorial.html
'''


class FomostoError(Exception):
    pass


nice_fatal_errors = (
    FomostoError,
    gf.StoreError,
    gf.meta.GridSpecError,
    gf.meta.OutOfBounds,
    util.DownloadError)


[docs] class FomostoArgumentParser(PyrockoArgumentParser): ''' Argument parser with the general options of all fomosto subcommands. ''' def __init__(self, *args, general_options=True, **kwargs): kwargs['add_help'] = False PyrockoArgumentParser.__init__(self, *args, **kwargs) group = self.add_argument_group('General options') group.add_argument( '--help', '-h', action='help', help='Show this help message and exit.') if general_options: loglevel_choices = [ 'critical', 'error', 'warning', 'info', 'debug'] loglevel_default = 'info' group.add_argument( '--loglevel', choices=loglevel_choices, default=loglevel_default, metavar='LEVEL', help='Set logger level. Choices: %s. Default: %s.' % ( ldq(loglevel_choices), dq(loglevel_default)))
def add_store_dir_argument(parser): parser.add_argument( 'store_dir', nargs='?', default=op.curdir, metavar='STORE_DIR', help='Store directory. Default: current directory.') def add_store_dirs_argument(parser): parser.add_argument( 'store_dirs', nargs='*', default=[op.curdir], metavar='STORE_DIR', help='Store directories. Default: current directory.') def add_force_argument(parser): parser.add_argument( '--force', action='store_true', help='Overwrite existing files.') def check_store_dir(store_dir): store_dir = op.abspath(store_dir) if not op.isdir(store_dir): raise FomostoError('Not a directory: %s' % store_dir) return store_dir def fomo_wrapper_module(name): if not re.match(gf.meta.StringID.pattern, name): raise FomostoError('Invalid modelling code ID: %s' % name) words = name.split('.', 1) if len(words) == 2: name, variant = words else: name = words[0] variant = None name_clean = re.sub(r'[.-]', '_', name) modname = '.'.join(['pyrocko', 'fomosto', name_clean]) try: mod = __import__(modname, level=0) except ImportError: raise FomostoError( 'Modelling code wrapper "%s" not available or not installed ' '(module probed: "%s").' % (name, modname)) return getattr(mod.fomosto, name_clean), variant def backends_details(): # The docs should not show what is installed on the machine building them. for_docs = os.environ.get('PYROCKO_RST_HELP', '0') != '0' lines = [] for modelling_code_id, missing in fomosto.get_backend_variants(): if for_docs: lines.append(' %s' % modelling_code_id) continue if missing is None: status = 'not usable, cannot import backend module' elif missing: status = 'not installed, missing: %s' % ', '.join(missing) else: status = 'installed' lines.append(' %-25s %s' % (modelling_code_id, status)) return '\n'.join(lines) def sindex(args): return '(%s)' % ', '.join('%g' % x for x in args) def load_config(fn): try: config = gf.meta.load(filename=fn) assert isinstance(config, gf.Config) except Exception: raise FomostoError('Cannot load GF config from file: %s' % fn) return config def setup_init(parser): parser.add_argument( 'modelling_code_id', metavar='MODELLING_CODE_ID', help='Modelling code to use, including its variant, e.g. ' '```qssp.2017```, or ```redeploy``` to create a store with the ' 'configuration of an existing one.') parser.add_argument( 'store_dir', metavar='STORE_DIR', help='Directory of the new store, or of the existing store to take ' 'the configuration from with ```redeploy```.') parser.add_argument( 'dest_dir', nargs='?', metavar='DESTINATION', help='Directory of the new store (only with ```redeploy```).') def run_init(parser, args): if args.modelling_code_id == 'redeploy': if args.dest_dir is None: parser.error('A destination is required with "redeploy".') source_dir, dest_dir = args.store_dir, args.dest_dir source = gf.Store(source_dir) config = copy.deepcopy(source.config) config.derived_from_id = source.config.id config_filenames = gf.store.Store.create_editables( dest_dir, config=config) dest = gf.Store(dest_dir) for k in source.extra_keys(): source_fn = source.get_extra_path(k) dest_fn = dest.get_extra_path(k) shutil.copyfile(source_fn, dest_fn) logger.info( '(1) Configure settings in files:\n %s' % '\n '.join(config_filenames)) logger.info( '(2) Run "fomosto redeploy <source> <dest>", as needed.') else: if args.dest_dir is not None: parser.error('Too many arguments.') store_dir = args.store_dir module, variant = fomo_wrapper_module(args.modelling_code_id) config_filenames = module.init(store_dir, variant) logger.info('(1) Configure settings in files:\n %s' % '\n '.join(config_filenames)) logger.info('(2) Run "fomosto ttt" in directory "%s".' % store_dir) logger.info('(3) Run "fomosto build" in directory "%s".' % store_dir) def setup_build(parser): add_store_dir_argument(parser) add_force_argument(parser) parser.add_argument( '--nworkers', type=int, metavar='N', help='Run N worker processes in parallel. Default: number of CPUs.') parser.add_argument( '--continue', dest='continue_', action='store_true', help='Continue suspended build.') parser.add_argument( '--step', type=int, metavar='I', help='Process step number I only.') parser.add_argument( '--block', dest='iblock', type=int, metavar='I', help='Process block number I only (requires ``--step`` for ' 'backends with several steps). With ```--block=0```, print ' 'the commands to process all blocks separately.') def run_build(parser, args): store_dir = check_store_dir(args.store_dir) step = args.step - 1 if args.step is not None else None iblock = args.iblock - 1 if args.iblock is not None else None store = gf.Store(store_dir) module, _ = fomo_wrapper_module(store.config.modelling_code_id) module.build( store_dir, force=args.force, nworkers=args.nworkers, continue_=args.continue_, step=step, iblock=iblock) def setup_stats(parser): add_store_dir_argument(parser) def run_stats(parser, args): store = gf.Store(check_store_dir(args.store_dir)) s = store.stats() for k in store.stats_keys: print('%s: %s' % (k, s[k])) def setup_check(parser): add_store_dir_argument(parser) def run_check(parser, args): store_dir = check_store_dir(args.store_dir) store = gf.Store(store_dir) problems = store.check(show_progress=True) if problems: raise FomostoError('Problems detected with GF store: %s' % store_dir) def setup_decimate(parser): add_store_dir_argument(parser) parser.add_argument( 'factor', type=int, metavar='FACTOR', help='Decimation factor.') parser.add_argument( '--config', dest='config_fn', metavar='FILE', help='Use modified spatial sampling given in FILE.') add_force_argument(parser) def run_decimate(parser, args): store_dir = check_store_dir(args.store_dir) config = None if args.config_fn: config = load_config(args.config_fn) store = gf.Store(store_dir) store.make_decimated( args.factor, config=config, force=args.force, show_progress=True) def setup_redeploy(parser): parser.add_argument( 'source_store_dir', metavar='SOURCE', help='Store to copy the traces from.') parser.add_argument( 'dest_store_dir', metavar='DESTINATION', help='Store to copy the traces into.') def run_redeploy(parser, args): source = gf.Store(args.source_store_dir) try: gf.store.Store.create_dependants(args.dest_store_dir) except gf.StoreError: pass dest = gf.Store(args.dest_store_dir, 'w') show_progress = True try: if show_progress: pbar = util.progressbar('redeploying', dest.config.nrecords) for i, node in enumerate(dest.config.iter_nodes()): try: tr = source.get(node, interpolation='off') dest.put(node, tr) except (gf.meta.OutOfBounds, gf.store.NotAllowedToInterpolate) as e: # noqa logger.debug('Skipping %s (%s).' % (sindex(node), e)) except gf.store.StoreError as e: logger.warning( 'Cannot insert %s (%s).' % (sindex(node), e)) if show_progress: pbar.update(i+1) finally: if show_progress: pbar.finish() def setup_trans2rot(parser): parser.add_argument( 'source_store_dir', metavar='SOURCE', help='Translational input store.') parser.add_argument( 'dest_store_dir', metavar='DESTINATION', help='Rotational output store.') def run_trans2rot(parser, args): from pyrocko.fomosto.elastic10_to_rotational8 \ import elastic10_to_rotational8 elastic10_to_rotational8(args.source_store_dir, args.dest_store_dir) def setup_fd2trans(parser): parser.add_argument( 'source_store_dir', metavar='SOURCE', help='Input store with finite difference support (elastic10_fd).') parser.add_argument( 'dest_store_dir', metavar='DESTINATION', help='Output store (elastic10).') def run_fd2trans(parser, args): from pyrocko.fomosto.elastic10_fd_to_elastic10 \ import elastic10_fd_to_elastic10 elastic10_fd_to_elastic10(args.source_store_dir, args.dest_store_dir) def setup_view(parser): add_store_dirs_argument(parser) parser.add_argument( '--extract', metavar='SELECTION', help='Specify which traces to show, as ```start:stop[:step|@num],' '...```, one range per store dimension.') parser.add_argument( '--show-phases', dest='showphases', metavar='PHASE_IDS', help='Add phase markers from the travel time tables. Comma-separated ' 'list of phase IDs, or ```all```.') parser.add_argument( '--opengl', dest='opengl', action='store_true', default=None, help='Use OpenGL for drawing.') parser.add_argument( '--no-opengl', dest='opengl', action='store_false', default=None, help='Do not use OpenGL for drawing.') def run_view(parser, args): gdef = None if args.extract: gdef = gf.meta.parse_grid_spec(args.extract) store_dirs = [check_store_dir(x) for x in args.store_dirs] alpha = 'abcdefghijklmnopqrstxyz'.upper() markers = [] traces = [] for istore, store_dir in enumerate(store_dirs): store = gf.Store(store_dir) if args.showphases == 'all': phasenames = [pn.id for pn in store.config.tabulated_phases] elif args.showphases is not None: phasenames = args.showphases.split(',') ii = 0 for node in store.config.iter_extraction(gdef): gtr = store.get(node) loc_code = '' if len(store_dirs) > 1: loc_code = alpha[istore % len(alpha)] if gtr: sta_code = '%04i (%s)' % ( ii, ','.join('%gk' % (x/1000.) for x in node[:-1])) tmin = gtr.deltat * gtr.itmin tr = trace.Trace( '', sta_code, loc_code, '%02i' % node[-1], ydata=gtr.data, deltat=gtr.deltat, tmin=tmin) if args.showphases: for phasename in phasenames: phase_tmin = store.t(phasename, node[:-1]) if phase_tmin: m = marker.PhaseMarker( [('', sta_code, loc_code, '%02i' % node[-1])], phase_tmin, phase_tmin, 0, phasename=phasename) markers.append(m) traces.append(tr) ii += 1 def setup(win): viewer = win.get_view() viewer.menuitem_showboxes.setChecked(False) viewer.menuitem_colortraces.setChecked(True) viewer.menuitem_demean.setChecked(False) viewer.menuitems_sorting[5][0].setChecked(True) viewer.menuitems_scaling[2][0].setChecked(True) viewer.sortingmode_change() viewer.scalingmode_change() trace.snuffle( traces, markers=markers, opengl=args.opengl, launch_hook=setup) def setup_extract(parser): add_store_dir_argument(parser) parser.add_argument( 'selection', metavar='SELECTION', help='Traces to extract, as ```start:stop[:step|@num],...```, one ' 'range per store dimension.') format_choices = ['mseed', 'sac', 'text', 'yaff'] format_default = 'mseed' parser.add_argument( '--format', default=format_default, choices=format_choices, metavar='FORMAT', help='Export to format FORMAT. Choices: %s. Default: %s.' % ( ldq(format_choices), dq(format_default))) fndfl = 'extracted/%(irecord)s_%(args)s.%(extension)s' parser.add_argument( '--output', dest='output_fn', default=fndfl, metavar='TEMPLATE', help='Output path template. Default: ```%s```.' % fndfl.replace('%', '%%')) def run_extract(parser, args): gdef = gf.meta.parse_grid_spec(args.selection) store_dir = check_store_dir(args.store_dir) extensions = { 'mseed': 'mseed', 'sac': 'sac', 'text': 'txt', 'yaff': 'yaff'} store = gf.Store(store_dir) for node in store.config.iter_extraction(gdef): gtr = store.get(node) if gtr: tr = trace.Trace( '', '', '', util.zfmt(store.config.ncomponents) % node[-1], ydata=gtr.data, deltat=gtr.deltat, tmin=gtr.deltat * gtr.itmin) additional = dict( args='_'.join('%g' % x for x in node), irecord=store.str_irecord(node), extension=extensions[args.format]) io.save( tr, args.output_fn, format=args.format, additional=additional) def setup_import(parser): parser.add_argument( 'source_path', metavar='SOURCE', help='Kiwi GFDB to import.') parser.add_argument( 'dest_store_dir', metavar='DESTINATION', help='GF store to create.') def run_import(parser, args): try: from tunguska import gfdb except ImportError: raise FomostoError( 'The Kiwi tools must be installed to use this feature.') show_progress = True source_path = args.source_path dest_store_dir = args.dest_store_dir if op.isdir(source_path): source_path = op.join(source_path, 'db') source_path = re.sub(r'(\.\d+\.chunk|\.index)$', '', source_path) db = gfdb.Gfdb(source_path) config = gf.meta.ConfigTypeA( id='imported_gfs', distance_min=db.firstx, distance_max=db.firstx + (db.nx-1) * db.dx, distance_delta=db.dx, source_depth_min=db.firstz, source_depth_max=db.firstz + (db.nz-1) * db.dz, source_depth_delta=db.dz, sample_rate=1.0/db.dt, ncomponents=db.ng ) gf.store.Store.create(dest_store_dir, config=config) dest = gf.Store(dest_store_dir, 'w') try: if show_progress: pbar = util.progressbar( 'importing', dest.config.nrecords/dest.config.ncomponents) for i, node in enumerate(dest.config.iter_nodes(level=-1)): source_depth, distance = [float(x) for x in node] traces = db.get_traces_pyrocko(distance, source_depth) ig_to_trace = dict((tr.meta['ig']-1, tr) for tr in traces) for ig in range(db.ng): if ig in ig_to_trace: tr = ig_to_trace[ig] gf_tr = gf.store.GFTrace( tr.get_ydata(), int(round(tr.tmin / tr.deltat)), tr.deltat) else: gf_tr = gf.store.Zero dest.put((source_depth, distance, ig), gf_tr) if show_progress: pbar.update(i+1) finally: if show_progress: pbar.finish() dest.close() def setup_export(parser): add_store_dir_argument(parser) parser.add_argument( 'target_path', metavar='DESTINATION', help='Kiwi GFDB to create. If it is a directory, the GFDB is ' 'created in it, with name ```kiwi_gfdb```.') parser.add_argument( '--nchunks', type=int, default=1, metavar='N', help='Split output GFDB into N chunks. Default: 1.') def run_export(parser, args): from subprocess import Popen, PIPE try: from tunguska import gfdb except ImportError as e: raise FomostoError( 'The Kiwi tools must be installed to use this feature (%s).' % e) show_progress = True target_path = args.target_path if op.isdir(target_path): target_path = op.join(target_path, 'kiwi_gfdb') logger.warning('Exported GFDB will be named "%s.*".' % target_path) source_store_dir = check_store_dir(args.store_dir) source = gf.Store(source_store_dir, 'r') config = source.config if not isinstance(config, gf.meta.ConfigTypeA): raise FomostoError( 'Only stores of type A can be exported to Kiwi format.') if op.isfile(target_path + '.index'): raise FomostoError('Destination already exists: %s' % target_path) cmd = [str(x) for x in [ 'gfdb_build', target_path, args.nchunks, config.ndistances, config.nsource_depths, config.ncomponents, config.deltat, config.distance_delta, config.source_depth_delta, config.distance_min, config.source_depth_min]] p = Popen(cmd, stdin=PIPE) p.communicate() out_db = gfdb.Gfdb(target_path) try: if show_progress: pbar = util.progressbar( 'exporting', config.nrecords/config.ncomponents) for i, (z, x) in enumerate(config.iter_nodes(level=-1)): data_out = [] for ig in range(config.ncomponents): try: tr = source.get((z, x, ig), interpolation='off') data_out.append((tr.t, tr.data * config.factor)) except gf.store.StoreError as e: logger.warning( 'Cannot get %s (%s).' % (sindex((z, x, ig)), e)) data_out.append(None) # put a zero valued sample to no-data zero-traces at a compatible # time tmins = [ entry[0][0] for entry in data_out if entry is not None and entry[0].size != 0] if tmins: tmin = min(tmins) for entry in data_out: if entry is not None and entry[0].size == 0: entry[0].resize(1) entry[1].resize(1) entry[0][0] = tmin entry[1][0] = 0.0 out_db.put_traces_slow(x, z, data_out) if show_progress: pbar.update(i+1) source.close() finally: if show_progress: pbar.finish() def stored_attribute_table_plots(phase_ids, args, attribute): import numpy as num from pyrocko.plot.cake_plot import xscaled, yscaled, mpl_init from pyrocko.plot import mpl_labelspace as labelspace plt = mpl_init() np = 1 store_dir = check_store_dir(args.store_dir) for phase_id in phase_ids: store = gf.Store(store_dir) if args.receiver_depth is not None: receiver_depth = args.receiver_depth * 1000.0 else: if isinstance(store.config, (gf.ConfigTypeA, gf.ConfigTypeC)): receiver_depth = store.config.receiver_depth elif isinstance(store.config, gf.ConfigTypeB): receiver_depth = store.config.receiver_depth_min else: receiver_depth = 0.0 phase = store.get_stored_phase(phase_id, attribute) axes = plt.subplot(2, len(phase_ids), np) labelspace(axes) xscaled(1. / km, axes) yscaled(1. / km, axes) x = None if isinstance(store.config, gf.ConfigTypeB): x = (receiver_depth, None, None) phase.plot_2d(axes, x=x) axes.set_title(phase_id) np += 1 axes = plt.subplot(2, 1, 2) num_d = 100 distances = num.linspace(store.config.distance_min, store.config.distance_max, num_d) if args.source_depth is not None: source_depth = args.source_depth * km else: source_depth = store.config.source_depth_min + ( store.config.source_depth_max - store.config.source_depth_min) / 2. if isinstance(store.config, gf.ConfigTypeA): attribute_vals = num.empty(num_d) for phase_id in phase_ids: attribute_vals[:] = num.nan for i, d in enumerate(distances): if attribute == 'phase': attribute_vals[i] = store.t(phase_id, (source_depth, d)) ylabel = 'TT [s]' else: attribute_vals[i] = store.get_stored_attribute( phase_id, args.attribute, (source_depth, d)) ylabel = '%s [deg]' % args.attribute axes.plot(distances / km, attribute_vals, label=phase_id) axes.set_title('source source_depth %s km' % (source_depth / km)) axes.set_xlabel('distance [km]') axes.set_ylabel(ylabel) axes.legend() plt.tight_layout() mpl_show(plt) def add_attribute_plot_arguments(parser): parser.add_argument( '--source-depth', type=float, metavar='FLOAT', help='Source depth [km]. Default: centre of the store\'s source ' 'depth range.') parser.add_argument( '--receiver-depth', type=float, metavar='FLOAT', help='Receiver depth [km]. Default: the store\'s (minimum) ' 'receiver depth.') def setup_ttt(parser): add_store_dir_argument(parser) add_force_argument(parser) def run_ttt(parser, args): store = gf.Store(check_store_dir(args.store_dir)) store.make_travel_time_tables(force=args.force) def setup_tttview(parser): add_store_dir_argument(parser) parser.add_argument( 'phase_ids', metavar='PHASE_IDS', help='Comma-separated list of phase IDs, e.g. ```Pdiff,S```.') add_attribute_plot_arguments(parser) def run_tttview(parser, args): phase_ids = args.phase_ids.split(',') stored_attribute_table_plots(phase_ids, args, attribute='phase') def setup_sat(parser): add_store_dir_argument(parser) add_force_argument(parser) attribute_choices = gf.store.available_stored_tables[1:] attribute_default = 'takeoff_angle' parser.add_argument( '--attribute', choices=attribute_choices, default=attribute_default, metavar='ATTRIBUTE', help='Calculate interpolation table for the given ray attribute. ' 'Choices: %s. Default: %s.' % ( ldq(attribute_choices), dq(attribute_default))) def run_sat(parser, args): store = gf.Store(check_store_dir(args.store_dir)) store.make_stored_table(args.attribute, force=args.force) def setup_satview(parser): add_store_dir_argument(parser) parser.add_argument( 'phase_ids', metavar='PHASE_IDS', help='Comma-separated list of phase IDs, e.g. ```Pdiff,S```.') add_attribute_plot_arguments(parser) attribute_choices = gf.store.available_stored_tables[1:] attribute_default = 'takeoff_angle' parser.add_argument( '--attribute', choices=attribute_choices, default=attribute_default, metavar='ATTRIBUTE', help='View the given ray attribute. Choices: %s. Default: %s.' % ( ldq(attribute_choices), dq(attribute_default))) def run_satview(parser, args): phase_ids = args.phase_ids.split(',') logger.info('Plotting stored attribute "%s".' % args.attribute) stored_attribute_table_plots(phase_ids, args, attribute=args.attribute) def setup_tttextract(parser): add_store_dir_argument(parser) parser.add_argument( 'phases', metavar='PHASES', help='Comma-separated list of phase IDs or timing definitions.') parser.add_argument( 'selection', metavar='SELECTION', help='Source/receiver configurations, as ' '```start:stop[:step|@num],...```, one range per store ' 'dimension (except the component dimension).') parser.add_argument( '--output', dest='output_fn', metavar='TEMPLATE', help='Output to text files instead of standard output, e.g. ' '```extracted/%%(args)s.txt```.') def run_tttextract(parser, args): try: phases = [gf.meta.Timing(x.strip()) for x in args.phases.split(',')] except gf.meta.InvalidTimingSpecification: parser.error('Invalid phase specification: "%s".' % args.phases) gdef = gf.meta.parse_grid_spec(args.selection) store_dir = check_store_dir(args.store_dir) store = gf.Store(store_dir) for node in store.config.iter_extraction(gdef, level=-1): s = ['%e' % x for x in node] for phase in phases: t = store.t(phase, node) if t is not None: s.append('%e' % t) else: s.append('nan') if args.output_fn: d = dict( args='_'.join('%e' % x for x in node), extension='txt') fn = args.output_fn % d util.ensuredirs(fn) with open(fn, 'a') as f: f.write(' '.join(s)) f.write('\n') else: print(' '.join(s)) def setup_tttlsd(parser): add_store_dir_argument(parser) parser.add_argument( 'phase_ids', metavar='PHASE_IDS', help='Comma-separated list of phase IDs.') def run_tttlsd(parser, args): phase_ids = [x.strip() for x in args.phase_ids.split(',')] store_dir = check_store_dir(args.store_dir) store = gf.Store(store_dir) for phase_id in phase_ids: store.fix_ttt_holes(phase_id) def setup_server(parser): from pyrocko import server parser.add_argument( 'store_superdirs', nargs='+', metavar='STORE_SUPER_DIR', help='Directories containing the stores to serve.') server.add_cli_arguments( parser, exclude=('page', 'open', 'cookie_secret_path'), default_port=8080) def run_server(parser, args): from pyrocko.gf import server engine = gf.LocalEngine(store_superdirs=args.store_superdirs) server.run( engine, host=args.host, port=args.port, debug=args.debug) def setup_download(parser): parser.add_argument( 'site', metavar='SITE', help='Server to download from, e.g. ```kinherd```.') parser.add_argument( 'store_id', nargs='?', metavar='STORE_ID', help='ID of the store to download. If not given, list the stores ' 'available on the server.') add_force_argument(parser) def run_download(parser, args): from pyrocko.gf import ws site, store_id = args.site, args.store_id if store_id is not None \ and not re.match(gf.meta.StringID.pattern, store_id): raise FomostoError('Invalid store ID: %s' % store_id) if site not in gf.ws.g_site_abbr: if -1 == site.find('://'): site = 'http://' + site ws.download_gf_store(site=site, store_id=store_id, force=args.force) def setup_modelview(parser): add_store_dirs_argument(parser) parser.add_argument( '--parameters', default='vp,vs', metavar='PARAMETERS', help='Comma-separated list of the parameters to plot. Choices: ' '```vp```, ```vs```, ```rho```, ```qp```, ```qs```, ' '```vp/vs```, ```qp/qs```. Default: ```vp,vs```.') def run_modelview(parser, args): import matplotlib.pyplot as plt import numpy as num from pyrocko.plot.cake_plot import mpl_init, xscaled, yscaled from pyrocko.plot import mpl_labelspace as labelspace mpl_init() neat_labels = { 'vp': '$v_p$', 'vs': '$v_s$', 'qp': '$Q_p$', 'qs': '$Q_s$', 'rho': '$\\rho$'} units = {'vp': '[km/s]', 'vs': '[km/s]', 'rho': '[g/cm^3]'} store_dirs = [check_store_dir(x) for x in args.store_dirs] parameters = args.parameters.split(',') fig, axes = plt.subplots(1, len(parameters), sharey=True, figsize=(len(parameters)*3, 5)) if not isinstance(axes, num.ndarray): axes = [axes] axes = dict(zip(parameters, axes)) for store_id in store_dirs: store = gf.Store(store_id) mod = store.config.earthmodel_1d z = mod.profile('z') for p in parameters: ax = axes[p] labelspace(ax) if '/' in p: p1, p2 = p.split('/') profile = mod.profile(p1)/mod.profile(p2) else: profile = mod.profile(p) ax.plot(profile, -z, label=store_id.split('/')[-1]) if p in ['vs', 'vp', 'rho']: xscaled(1./1000., ax) yscaled(1./km, ax) for p, ax in axes.items(): ax.grid() if p in neat_labels: lab = neat_labels[p] elif all(x in neat_labels for x in p.split('/')): lab = '/'.join(neat_labels[x] for x in p.split('/')) else: lab = p ax.set_title(lab, y=1.02) if p in units: lab += ' ' + units[p] ax.autoscale() ax.set_xlabel(lab) axes[parameters[0]].set_ylabel('Depth [km]') handles, labels = ax.get_legend_handles_labels() if len(store_dirs) > 1: ax.legend( handles, labels, bbox_to_anchor=(0.5, 0.12), bbox_transform=fig.transFigure, ncol=3, loc='upper center', fancybox=True) plt.subplots_adjust(bottom=0.22, wspace=0.05) mpl_show(plt) def setup_upgrade(parser): add_store_dirs_argument(parser) def run_upgrade(parser, args): store_dirs = [check_store_dir(x) for x in args.store_dirs] for store_dir in store_dirs: store = gf.Store(store_dir) nup = store.upgrade() if nup == 0: print('%s: already up-to-date.' % store_dir) else: print('%s: %i file%s upgraded.' % ( store_dir, nup, ['s', ''][nup == 1])) def setup_addref(parser): parser.add_argument( 'paths', nargs='+', metavar='PATH', help='Store directories (default: current directory) and BibTeX ' 'files with the references to add.') def run_addref(parser, args): store_dirs = [] filenames = [] for path in args.paths: if op.isdir(path): store_dirs.append(path) elif op.isfile(path): filenames.append(path) else: raise FomostoError('Invalid argument: %s' % path) if not store_dirs: store_dirs.append('.') references = [] try: for filename in filenames: references.extend(gf.meta.Reference.from_bibtex(filename=filename)) except ImportError: raise FomostoError( 'The pybtex module must be installed to use this function.') if not references: raise FomostoError('No references found.') for store_dir in store_dirs: store = gf.Store(store_dir) ids = [ref.id for ref in store.config.references] for ref in references: if ref.id in ids: raise FomostoError('Duplicate reference ID: %s' % ref.id) ids.append(ref.id) store.config.references.append(ref) store.save_config(make_backup=True) def setup_qc(parser): add_store_dir_argument(parser) def run_qc(parser, args): store = gf.Store(check_store_dir(args.store_dir)) s = store.stats() if s['empty'] != 0: print('has empty records') for aname in ['author', 'author_email', 'description', 'references']: if not getattr(store.config, aname): print('%s empty' % aname) def setup_report(parser): parser.add_argument( 'report_args', nargs=argparse.REMAINDER, metavar='...', help='Subcommand and arguments of the report tool. Run ' '```fomosto report``` without arguments to list its ' 'subcommands.') def run_report(parser, args): from pyrocko.fomosto.report import report_call report_call.run_program(args.report_args) subcommands = [ ('init', 'Create a new empty GF store.', ''' The new store is configured for the given modelling code, which must include the variant of the backend, e.g. ```qssp.2017```. With ```fomosto init redeploy SOURCE DESTINATION```, a new store is created with the configuration of an existing store. The following modelling backends and variants are available. %s Installation instructions and more information at https://pyrocko.org/docs/current/apps/fomosto/backends.html '''), ('build', 'Compute GFs and fill into store.', None), ('stats', 'Print information about a GF store.', None), ('check', 'Check for problems in GF store.', None), ('decimate', 'Build decimated variant of a GF store.', None), ('redeploy', 'Copy traces from one GF store into another.', None), ('view', 'View selected traces.', None), ('extract', 'Extract selected traces.', None), ('import', 'Convert Kiwi GFDB to GF store format.', None), ('export', 'Convert GF store to Kiwi GFDB format.', None), ('ttt', 'Create travel time tables.', None), ('tttview', 'Plot travel time table.', None), ('tttextract', 'Extract selected travel times.', None), ('tttlsd', 'Fix holes in travel time tables.', ''' This subcommand fills holes in travel time tables by using an eikonal solver to predict travel times for the missing values. The approach works best for simple P or S phases without any reflections or conversions. It creates new travel time tables which are named by adding the suffix ```.lsd``` to the original name. E.g. running ```fomosto tttlsd begin``` would produce a new travel time table ```begin.lsd```. The new name can be referenced anywhere where a stored travel time table can be used, e.g. in ```extra/qseis```. '''), ('sat', 'Create stored ray attribute table.', None), ('satview', 'Plot stored ray attribute table.', None), ('server', 'Run seismosizer server.', None), ('download', 'Download GF store from a server.', ''' Browse pre-calculated Green's function stores online: https://greens-mill.pyrocko.org '''), ('modelview', 'Plot earth models.', None), ('upgrade', 'Upgrade store format to latest version.', None), ('addref', 'Import citation references to GF store config.', None), ('qc', 'Check GF store for missing data and metadata.', None), ('report', 'Create reports for Green\'s function databases.', None), ('trans2rot', 'Create rotational from translational store.', ''' Create a rotational GF store from a translational one using finite differences approximations (elastic10 or elastic10_fd to rotational8). If the source store is of type A and receivers at the surface, the free surface condition is used to replace the vertical derivatives with horizontal ones. If the input is of type B, the complete set of derivatives are used. It is important that the spatial sampling of the source store is high enough so that the finite difference approximations are sufficiently accurate. If the destination store directory does not exist, it is created and the extent and spacing of the source store is used for the rotational store. If it does exist and contains a valid ```config```, it is used in place in order to allow for different input and output extents and spacings. The store must be empty in the latter case (delete the ```index``` and ```traces``` files if needed). '''), ('fd2trans', 'Extract elastic10 from elastic10_fd store.', ''' Extract regular GF store (elastic10) from a finite difference support GF store (elastic10_fd). '''), ] def make_parser(): parser = FomostoArgumentParser( prog=program_name, description=description, epilog=epilog, general_options=False) subparsers = parser.add_subparsers( dest='subcommand', metavar='SUBCOMMAND', title='Subcommands') for name, headline, details in subcommands: subdescription = headline if details: if name == 'init': details = details % backends_details() subdescription += '\n' + details subparser = subparsers.add_parser( name, help=headline, description=subdescription, epilog=epilog, # the report tool parses its arguments itself general_options=name != 'report') globals()['setup_' + name](subparser) subparser.set_defaults( target=globals()['run_' + name], subparser=subparser) return parser
[docs] def main(args=None): ''' CLI entry point for Pyrocko's ``fomosto`` app. ''' if args is None: args = sys.argv[1:] args = list(args) if args and args[0] == 'help': args = args[1:2] + ['--help'] if args and args[0] == 'report' and args[1:] not in (['--help'], ['-h']): # The report tool parses its arguments itself. util.setup_logging(program_name, 'info') from pyrocko.fomosto.report import report_call report_call.run_program(args[1:]) return parser = make_parser() args = parser.parse_args(args) if 'target' not in args: parser.print_help() return util.setup_logging(program_name, getattr(args, 'loglevel', 'info')) try: args.target(args.subparser, args) except nice_fatal_errors as e: logger.fatal(str(e)) sys.exit(1)
if __name__ == '__main__': main()