lassie 10.3 KB
Newer Older
Sebastian Heimann's avatar
Sebastian Heimann committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python

import sys
import logging
from optparse import OptionParser

from pyrocko import util

import lassie

logger = logging.getLogger('main')


def d2u(d):
    if isinstance(d, dict):
Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
16
        return dict((k.replace('-', '_'), v) for (k, v) in d.items())
Sebastian Heimann's avatar
Sebastian Heimann committed
17
18
19
20
    else:
        return d.replace('-', '_')


21
22
23
def str_to_time(s):
    try:
        return util.str_to_time(s)
Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
24
    except util.TimeStrError as e:
25
26
27
        raise lassie.LassieError(str(e))


Sebastian Heimann's avatar
Sebastian Heimann committed
28
29
subcommand_descriptions = {
    'init': 'create initial configuration file',
Sebastian Heimann's avatar
Sebastian Heimann committed
30
    'search': 'detect seismic events',
Marius Kriegerowski's avatar
Marius Kriegerowski committed
31
32
    'map-geometry': 'make station map',
    'snuffle': 'snuffle'
Sebastian Heimann's avatar
Sebastian Heimann committed
33
34
35
36
}

subcommand_usages = {
    'init': 'init',
Sebastian Heimann's avatar
Sebastian Heimann committed
37
    'search': 'search <configfile> [options]',
Marius Kriegerowski's avatar
Marius Kriegerowski committed
38
    'map-geometry': 'map-geometry <configfile> [options] <output.(png|pdf)',
Marius Kriegerowski's avatar
Marius Kriegerowski committed
39
    'snuffle': 'snuffle <configfile>',
Sebastian Heimann's avatar
Sebastian Heimann committed
40
41
42
43
44
45
46
47
48
49
50
51
52
53
}

subcommands = subcommand_descriptions.keys()

program_name = 'lassie'

usage_tdata = d2u(subcommand_descriptions)
usage_tdata['program_name'] = program_name

usage = program_name + ''' <subcommand> [options] [--] <arguments> ...

Subcommands:

    init          %(init)s
Sebastian Heimann's avatar
Sebastian Heimann committed
54
    search        %(search)s
Sebastian Heimann's avatar
Sebastian Heimann committed
55
    map-geometry  %(map_geometry)s
Marius Kriegerowski's avatar
Marius Kriegerowski committed
56
    snuffle       %(snuffle)s
Sebastian Heimann's avatar
Sebastian Heimann committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

To get further help and a list of available options for any subcommand run:

    %(program_name)s <subcommand> --help

''' % usage_tdata


def add_common_options(parser):
    parser.add_option(
        '--loglevel',
        action='store',
        dest='loglevel',
        type='choice',
        choices=('critical', 'error', 'warning', 'info', 'debug'),
        default='info',
Sebastian Heimann's avatar
flake8    
Sebastian Heimann committed
73
74
75
        help='set logger level to '
             '"critical", "error", "warning", "info", or "debug". '
             'Default is "%default".')
Sebastian Heimann's avatar
Sebastian Heimann committed
76
77
78
79
80
81
82
83
84
85


def process_common_options(options):
    util.setup_logging(program_name, options.loglevel)


def cl_parse(command, args, setup=None):
    usage = subcommand_usages[command]
    descr = subcommand_descriptions[command]

Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
86
    if isinstance(usage, str):
Sebastian Heimann's avatar
Sebastian Heimann committed
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
        usage = [usage]

    susage = '%s %s' % (program_name, usage[0])
    for s in usage[1:]:
        susage += '\n%s%s %s' % (' '*7, program_name, s)

    parser = OptionParser(
        usage=susage,
        description=descr[0].upper() + descr[1:] + '.')

    if setup:
        setup(parser)

    add_common_options(parser)
    (options, args) = parser.parse_args(args)
    process_common_options(options)
    return parser, options, args


def die(message, err=''):
    if err:
        sys.exit('%s: error: %s \n %s' % (program_name, message, err))
    else:
        sys.exit('%s: error: %s' % (program_name, message))


def help_and_die(parser, message):
    parser.print_help(sys.stderr)
    sys.stderr.write('\n')
    die(message)


def escape(s):
    return s.replace("'", "\\'")

Sebastian Heimann's avatar
Sebastian Heimann committed
122

Sebastian Heimann's avatar
Sebastian Heimann committed
123
124
125
126
def command_init(args):
    def setup(parser):
        parser.add_option(
            '--stations', dest='stations_path',
Sebastian Heimann's avatar
Sebastian Heimann committed
127
            metavar='PATH',
Sebastian Heimann's avatar
Sebastian Heimann committed
128
129
            help='stations file')

Sebastian Heimann's avatar
Sebastian Heimann committed
130
131
132
133
134
135
136
        parser.add_option(
            '--data', dest='data_paths',
            default=[],
            action='append',
            metavar='PATH',
            help='data directory (this option may be repeated)')

Sebastian Heimann's avatar
Sebastian Heimann committed
137
138
    parser, options, args = cl_parse('init', args, setup=setup)

Sebastian Heimann's avatar
Sebastian Heimann committed
139
140
141
    if options.data_paths:
        s_data_paths = '\n'.join(
            "- '%s'" % escape(x) for x in options.data_paths)
Sebastian Heimann's avatar
Sebastian Heimann committed
142
    else:
Sebastian Heimann's avatar
Sebastian Heimann committed
143
        s_data_paths = "- 'DATA'"
Sebastian Heimann's avatar
Sebastian Heimann committed
144
145
146
147
148
149

    if options.stations_path:
        stations_path = options.stations_path
    else:
        stations_path = 'STATIONS_PATH'

Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
150
    print('''%%YAML 1.1
Sebastian Heimann's avatar
Sebastian Heimann committed
151
152
153
--- !lassie.Config

## Configuration file for Lassie, your friendly earthquake detector
Sebastian Heimann's avatar
Sebastian Heimann committed
154
##
Sebastian Heimann's avatar
Sebastian Heimann committed
155
156
157
## Receiver coordinates can be read from a stations file in Pyrocko format:
stations_path: '%(stations_path)s'

Sebastian Heimann's avatar
Sebastian Heimann committed
158
159
## Receivers can also be listed in the config file, lat/lon and carthesian
## (x/y/z) = (North/East/Down) coordinates are supported and may be combined
Sebastian Heimann's avatar
Sebastian Heimann committed
160
161
162
163
164
165
166
167
168
169
170
171
172
## (interpreted as reference + offset). Omitted values are treated as zero.
# receivers:
# - !lassie.Receiver
#   codes: ['', 'ACC13', '']
#   lat: 10.
#   lon: 12.
#   x: 2397.56
#   y: 7331.94
#   z: -404.1

## List of data directories. Lassie will recurse into subdirectories to find
## all contained waveform files.
data_paths:
Sebastian Heimann's avatar
Sebastian Heimann committed
173
%(s_data_paths)s
Sebastian Heimann's avatar
Sebastian Heimann committed
174

Sebastian Heimann's avatar
Sebastian Heimann committed
175
176
177
178
## name template for Lassie's output directory. The placeholder
## "${config_name}" will be replaced with the basename of the config file.
run_path: '${config_name}.turd'

Sebastian Heimann's avatar
Sebastian Heimann committed
179
## Processing time interval (default: use time interval of available data)
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# tmin: '2012-02-06 04:20:00'
# tmax: '2012-02-06 04:30:00'

## Search grid; if not given here (if commented), a default grid will be chosen
# grid: !lassie.Carthesian3DGrid
#   lat: 38.7
#   lon: -7.9
#   xmin: -70e3  # in [m]
#   xmax: 70e3
#   ymin: -70e3
#   ymax: 70e3
#   zmin: 0.0
#   zmax: 0.0
#   dx: 2.5e3
#   dy: 2.5e3
#   dz: 2.5e3
Sebastian Heimann's avatar
Sebastian Heimann committed
196
197
198
199
200
201
202

## Size factor to use when automatically choosing a grid size
autogrid_radius_factor: 1.5

## Grid density factor used when automatically choosing a grid
autogrid_density_factor: 10.0

Sebastian Heimann's avatar
Sebastian Heimann committed
203
## Composition of image function
204
image_function_contributions:
Sebastian Heimann's avatar
Sebastian Heimann committed
205
206

- !lassie.OnsetIFC
Sebastian Heimann's avatar
Sebastian Heimann committed
207
  name: 'P'
Sebastian Heimann's avatar
Sebastian Heimann committed
208
  weight: 30.0
Sebastian Heimann's avatar
Sebastian Heimann committed
209
  fmin: 1.0
210
  fmax: 15.0
Sebastian Heimann's avatar
Sebastian Heimann committed
211
212
213
214
215
216
217
218
219
220
  short_window: 1.0
  window_ratio: 8.0
  fsmooth: 0.2
  fnormalize: 0.02
  #shifter: !lassie.VelocityShifter
  #  velocity: 6000.
  shifter: !lassie.CakePhaseShifter
    timing: '{stored:p}'
    earthmodel_id: 'swiss'

Sebastian Heimann's avatar
Sebastian Heimann committed
221
222
223
224
- !lassie.WavePacketIFC
  name: 'S'
  weight: 1.0
  fmin: 1.0
Sebastian Heimann's avatar
Sebastian Heimann committed
225
226
227
228
229
230
231
232
233
  fmax: 8.0
  fsmooth: 0.05
  #shifter: !lassie.VelocityShifter
  #  velocity: 3300.
  shifter: !lassie.CakePhaseShifter
    # factor: 1.0
    # offset: 1.0
    timing: '{stored:s}'
    earthmodel_id: 'swiss'
Sebastian Heimann's avatar
Sebastian Heimann committed
234
235

## Whether to divide image function frames by their mean value
Sebastian Heimann's avatar
Sebastian Heimann committed
236
sharpness_normalization: false
Sebastian Heimann's avatar
Sebastian Heimann committed
237
238

## Threshold on detector function
Sebastian Heimann's avatar
Sebastian Heimann committed
239
240
241
242
243
244
detector_threshold: 150.

## Whether to create a figure for every detection and save it in the output
## directory
save_figures: true

Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
245
## Mapping of phase ID to phase definition in cake syntax (used e.g. in the
Sebastian Heimann's avatar
Sebastian Heimann committed
246
247
248
249
250
251
252
253
254
## CakePhaseShifter config sections)
tabulated_phases:
- !pf.TPDef
  id: 'p'
  definition: 'P,p'
- !pf.TPDef
  id: 's'
  definition: 'S,s'

Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
255
## Mapping of earthmodel ID  to the actual earth model in nd format (used in
Sebastian Heimann's avatar
Sebastian Heimann committed
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
## the CakePhaseShifter config sections)
earthmodels:
- !lassie.CakeEarthmodel
  id: 'swiss'
  earthmodel_1d: |2
    0.0 5.53 3.10  2.75
    2.0 5.53 3.10  2.75
    2.0 5.80 3.25  2.75
    5.0 5.80 3.25  2.75
    5.0 5.83 3.27  2.75
    8.0 5.83 3.27  2.75
    8.0 5.95 3.34  2.8
    13.0 5.95 3.34  2.8
    13.0 5.96 3.34  2.8
    22.0 5.96 3.34  2.8
    22.0 6.53 3.66  2.8
    30.0 6.53 3.66  2.8
    30.0 7.18 4.03 3.3
    40.0 7.18 4.03 3.3
    40.0 7.53 4.23 3.3
    50.0 7.53 4.23 3.3
    50.0 7.83 4.39 3.3
    60.0 7.83 4.39 3.3
    60.0 8.15 4.57 3.3
    120.0 8.15 4.57 3.3
Sebastian Heimann's avatar
Sebastian Heimann committed
281
''' % dict(
Sebastian Heimann's avatar
Sebastian Heimann committed
282
        stations_path=stations_path,
Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
283
        s_data_paths=s_data_paths))
Sebastian Heimann's avatar
Sebastian Heimann committed
284
285


Sebastian Heimann's avatar
Sebastian Heimann committed
286
def command_search(args):
Sebastian Heimann's avatar
Sebastian Heimann committed
287
288
289
290
291
292
293
294
295
296
297
298
299
    def setup(parser):
        parser.add_option(
            '--force', dest='force', action='store_true',
            help='overwrite existing files')

        parser.add_option(
            '--show-detections', dest='show_detections', action='store_true',
            help='show plot for every detection found')

        parser.add_option(
            '--show-movie', dest='show_movie', action='store_true',
            help='show movie when showing detections')

300
        parser.add_option(
Sebastian Heimann's avatar
flake8    
Sebastian Heimann committed
301
302
            '--show-window-traces', dest='show_window_traces',
            action='store_true',
303
304
            help='show preprocessed traces for every processing time window')

Sebastian Heimann's avatar
Sebastian Heimann committed
305
306
307
308
        parser.add_option(
            '--stop-after-first', dest='stop_after_first', action='store_true',
            help='show plot for every detection found')

309
310
311
312
313
314
315
316
317
318
        parser.add_option(
            '--tmin', dest='tmin', metavar="'YYYY-MM-DD HH:MM:SS.XXX'",
            help='beginning of processing time window '
                 '(overrides config file settings)')

        parser.add_option(
            '--tmax', dest='tmax', metavar="'YYYY-MM-DD HH:MM:SS.XXX'",
            help='end of processing time window '
                 '(overrides config file settings)')

Marius Kriegerowski's avatar
Marius Kriegerowski committed
319
320
321
322
        parser.add_option(
            '--nworkers', dest='nworkers', metavar="N",
            help='use N cpus in parallel')

Sebastian Heimann's avatar
bark    
Sebastian Heimann committed
323
324
325
326
        parser.add_option(
            '--speak', dest='bark', action='store_true',
            help='alert on detection of events')

Sebastian Heimann's avatar
Sebastian Heimann committed
327
    parser, options, args = cl_parse('search', args, setup=setup)
Sebastian Heimann's avatar
Sebastian Heimann committed
328
329
330
331
332
333
    if len(args) != 1:
        help_and_die(parser, 'missing argument')

    config_path = args[0]
    config = lassie.read_config(config_path)
    try:
334
335
336
337
338
339
340
341
        tmin = tmax = None

        if options.tmin:
            tmin = str_to_time(options.tmin)

        if options.tmax:
            tmax = str_to_time(options.tmax)

Marius Kriegerowski's avatar
Marius Kriegerowski committed
342
343
344
345
346
        if options.nworkers:
            nparallel = int(options.nworkers)
        else:
            nparallel = None

Sebastian Heimann's avatar
Sebastian Heimann committed
347
        lassie.search(
Sebastian Heimann's avatar
Sebastian Heimann committed
348
            config,
349
350
            override_tmin=tmin,
            override_tmax=tmax,
Sebastian Heimann's avatar
Sebastian Heimann committed
351
352
353
            force=options.force,
            show_detections=options.show_detections,
            show_movie=options.show_movie,
354
            show_window_traces=options.show_window_traces,
Marius Kriegerowski's avatar
Marius Kriegerowski committed
355
            stop_after_first=options.stop_after_first,
Sebastian Heimann's avatar
bark    
Sebastian Heimann committed
356
357
            nparallel=nparallel,
            bark=options.bark)
Sebastian Heimann's avatar
Sebastian Heimann committed
358

Marius Kriegerowski's avatar
wip py3    
Marius Kriegerowski committed
359
    except lassie.LassieError as e:
Sebastian Heimann's avatar
Sebastian Heimann committed
360
361
362
363
364
365
366
367
368
369
370
371
372
373
        die(str(e))


def command_map_geometry(args):
    parser, options, args = cl_parse('map-geometry', args)
    if len(args) != 2:
        help_and_die(parser, 'missing arguments')

    config_path = args[0]
    output_path = args[1]
    config = lassie.read_config(config_path)
    lassie.map_geometry(config, output_path)


Marius Kriegerowski's avatar
Marius Kriegerowski committed
374
375
376
377
378
379
380
381
382
383
384
def command_snuffle(args):
    parser, options, args = cl_parse('snuffle', args)
    if len(args) != 1:
        help_and_die(parser, 'missing arguments')

    config_path = args[0]
    config = lassie.read_config(config_path)

    lassie.snuffle(config)


Sebastian Heimann's avatar
Sebastian Heimann committed
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
if __name__ == '__main__':

    usage_sub = 'fomosto %s [options]'
    if len(sys.argv) < 2:
        sys.exit('Usage: %s' % usage)

    args = list(sys.argv)
    args.pop(0)
    command = args.pop(0)

    if command in subcommands:
        globals()['command_' + d2u(command)](args)

    elif command in ('--help', '-h', 'help'):
        if command == 'help' and args:
            acommand = args[0]
            if acommand in subcommands:
                globals()['command_' + acommand](['--help'])

        sys.exit('Usage: %s' % usage)

    else:
        die('no such subcommand: %s' % command)