Coverage for /usr/local/lib/python3.13/dist-packages/pyrocko/squirrel/tool/commands/jackseis.py: 24%

374 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-12-04 10:41 +0000

1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6''' 

7Implementation of :app:`squirrel jackseis`. 

8''' 

9 

10import re 

11import sys 

12import logging 

13import os.path as op 

14 

15import numpy as num 

16 

17from pyrocko import io, trace, util 

18from pyrocko import progress 

19from pyrocko.has_paths import Path, HasPaths 

20from pyrocko.guts import Dict, String, Choice, Float, Bool, List, Timestamp, \ 

21 StringChoice, IntChoice, Defer, load_all, clone 

22 

23from pyrocko.squirrel.base import Squirrel 

24from pyrocko.squirrel.dataset import Dataset 

25from pyrocko.squirrel.client.local import LocalData 

26from pyrocko.squirrel.error import ToolError, SquirrelError 

27from pyrocko.squirrel.model import CodesNSLCE, QuantityType 

28from pyrocko.squirrel.operators.base import NetworkGrouping, StationGrouping, \ 

29 ChannelGrouping, SensorGrouping 

30from pyrocko.squirrel.storage import StorageSchemeChoice, StorageScheme, \ 

31 StorageSchemeLayout, get_storage_scheme 

32from pyrocko.squirrel.tool.common import ldq 

33 

34tts = util.time_to_str 

35 

36guts_prefix = 'jackseis' 

37logger = logging.getLogger('psq.cli.jackseis') 

38 

39 

40g_filenames_all = set() 

41 

42 

43def check_append_hook(fn): 

44 return fn in g_filenames_all 

45 

46 

47def dset(kwargs, keys, values): 

48 for k, v in zip(keys, values): 

49 kwargs[k] = v 

50 

51 

52def make_task(*args): 

53 return progress.task(*args, logger=logger) 

54 

55 

56def parse_rename_rule_from_string(s): 

57 s = s.strip() 

58 if re.match(r'^([^:,]*:[^:,]*,?)+', s): 

59 return dict( 

60 x.split(':') for x in s.strip(',').split(',')) 

61 else: 

62 return s 

63 

64 

65class JackseisError(ToolError): 

66 pass 

67 

68 

69class Chain(object): 

70 def __init__(self, node, parent=None): 

71 self.node = node 

72 self.parent = parent 

73 

74 def mcall(self, name, *args, **kwargs): 

75 ret = [] 

76 if self.parent is not None: 

77 ret.append(self.parent.mcall(name, *args, **kwargs)) 

78 

79 ret.append(getattr(self.node, name)(*args, **kwargs)) 

80 return ret 

81 

82 def fcall(self, name, *args, **kwargs): 

83 v = getattr(self.node, name)(*args, **kwargs) 

84 if v is None and self.parent is not None: 

85 return self.parent.fcall(name, *args, **kwargs) 

86 else: 

87 return v 

88 

89 def get(self, name): 

90 v = getattr(self.node, name) 

91 if v is None and self.parent is not None: 

92 return self.parent.get(name) 

93 else: 

94 return v 

95 

96 def dget(self, name, k): 

97 v = getattr(self.node, name).get(k, None) 

98 if v is None and self.parent is not None: 

99 return self.parent.dget(name, k) 

100 else: 

101 return v 

102 

103 

104class OutputFormatChoice(StringChoice): 

105 choices = io.allowed_formats('save') 

106 

107 

108class OutputDataTypeChoice(StringChoice): 

109 choices = ['int32', 'int64', 'float32', 'float64'] 

110 name_to_dtype = { 

111 'int32': num.int32, 

112 'int64': num.int64, 

113 'float32': num.float32, 

114 'float64': num.float64} 

115 

116 

117class TraversalChoice(StringChoice): 

118 choices = ['network', 'station', 'channel', 'sensor'] 

119 name_to_grouping = { 

120 'network': NetworkGrouping(), 

121 'station': StationGrouping(), 

122 'sensor': SensorGrouping(), 

123 'channel': ChannelGrouping()} 

124 

125 

126class InstrumentCorrectionMode(StringChoice): 

127 choices = ['complete', 'sensor'] 

128 

129 

130class Converter(HasPaths): 

131 

132 in_dataset = Dataset.T(optional=True) 

133 in_path = String.T(optional=True) 

134 in_paths = List.T(String.T(optional=True)) 

135 

136 codes = List.T(CodesNSLCE.T(), optional=True) 

137 

138 rename = Dict.T( 

139 String.T(), 

140 Choice.T([ 

141 String.T(), 

142 Dict.T(String.T(), String.T())])) 

143 tmin = Timestamp.T(optional=True) 

144 tmax = Timestamp.T(optional=True) 

145 tinc = Float.T(optional=True) 

146 

147 downsample = Float.T(optional=True) 

148 

149 quantity = QuantityType.T(optional=True) 

150 fmin = Float.T(optional=True) 

151 fmax = Float.T(optional=True) 

152 fcut_factor = Float.T(optional=True) 

153 fmin_cut = Float.T(optional=True) 

154 fmax_cut = Float.T(optional=True) 

155 instrument_correction_mode = InstrumentCorrectionMode.T( 

156 default='complete') 

157 

158 rotate_to_enz = Bool.T(default=False) 

159 

160 out_path = Path.T(optional=True) 

161 out_sds_path = Path.T(optional=True) 

162 out_storage_path = Path.T(optional=True) 

163 out_storage_scheme = StorageSchemeChoice.T(default='default') 

164 out_format = OutputFormatChoice.T(optional=True) 

165 out_data_type = OutputDataTypeChoice.T(optional=True) 

166 out_mseed_record_length = IntChoice.T( 

167 optional=True, 

168 choices=list(io.mseed.VALID_RECORD_LENGTHS)) 

169 out_mseed_steim = IntChoice.T( 

170 optional=True, 

171 choices=[1, 2]) 

172 out_meta_path = Path.T(optional=True) 

173 

174 traversal = TraversalChoice.T(optional=True) 

175 

176 parts = List.T(Defer('Converter.T')) 

177 

178 def get_effective_frequency_taper(self, chain): 

179 fmin = chain.get('fmin') 

180 fmax = chain.get('fmax') 

181 

182 if None in (fmin, fmax): 

183 if fmin is not None: 

184 raise JackseisError('Converter: fmax not specified.') 

185 if fmax is not None: 

186 raise JackseisError('Converter: fmin not specified.') 

187 

188 return None 

189 

190 fcut_factor = chain.get('fcut_factor') or 2.0 

191 fmin_cut = chain.get('fmin_cut') 

192 fmax_cut = chain.get('fmax_cut') 

193 fmin_cut = fmin_cut if fmin_cut is not None else fmin / fcut_factor 

194 fmax_cut = fmax_cut if fmax_cut is not None else fmax * fcut_factor 

195 

196 return fmin_cut, fmin, fmax, fmax_cut 

197 

198 @classmethod 

199 def add_arguments(cls, p): 

200 p.add_squirrel_query_arguments(without=['time']) 

201 

202 p.add_argument( 

203 '--tinc', 

204 dest='tinc', 

205 type=float, 

206 metavar='SECONDS', 

207 help='Set time length of output files [s].') 

208 

209 p.add_argument( 

210 '--downsample', 

211 dest='downsample', 

212 type=float, 

213 metavar='RATE', 

214 help='Downsample to RATE [Hz].') 

215 

216 p.add_argument( 

217 '--quantity', 

218 dest='quantity', 

219 metavar='QUANTITY', 

220 choices=QuantityType.choices, 

221 help=''' 

222Restitute waveforms to selected ``QUANTITY``. Restitution is performed by 

223multiplying the waveform spectra with a tapered inverse of the instrument 

224response transfer function. The frequency band of the taper can be adjusted 

225using the ``--band`` option. Choices: %s. 

226'''.strip() % ldq(QuantityType.choices)) 

227 

228 p.add_argument( 

229 '--band', 

230 metavar='FMIN,FMAX or FMIN,FMAX,CUTFACTOR or ' 

231 'FMINCUT,FMIN,FMAX,FMAXCUT', 

232 help=''' 

233Frequency band used in restitution (see ``--quantity``) or for (acausal) 

234filtering. Waveform spectra are multiplied with a taper with cosine-shaped 

235flanks and which is flat between ``FMIN`` and ``FMAX``. The flanks of the taper 

236drop to zero at ``FMINCUT`` and ``FMAXCUT``. If ``CUTFACTOR`` is given, 

237``FMINCUT`` and ``FMAXCUT`` are set to ``FMIN/CUTFACTOR`` and 

238``FMAX*CUTFACTOR`` respectively. ``CUTFACTOR`` defaults to 2. 

239'''.strip()) 

240 

241 p.add_argument( 

242 '--instrument-correction-mode', 

243 dest='instrument_correction_mode', 

244 choices=['complete', 'sensor'], 

245 default='complete', 

246 help=''' 

247Select mode of the instrument correction when performing a restition with 

248``--quantity``. This option selects which stages of the instrument response 

249should be considered completely, i.e. including their frequency dependence, and 

250which stages should be considered by only considering their overall gain 

251factor. Choices: ``complete`` -- all stages are considered completely 

252(default). ``sensor`` -- only the first stage of the insrument response is 

253treated completely. The first stage of the instrument response conventionally 

254represents the characteristics of the sensor and is usually given in poles and 

255zeros representation. The frequency response of the FIR filters of the 

256digitizer's downsampling stages are not considered in ``sensor`` mode. Instead, 

257replacement gain factors are computed by evaluating the frequency response of 

258the respective stages at the lower frequency bound of the restitution ``FMIN`` 

259(see ``--band``). The assumption here is, that the decimation FIR filters are 

260flat at this frequency and representative for the whole pass band. 

261'''.strip()) 

262 

263 p.add_argument( 

264 '--rotate-to-enz', 

265 action='store_true', 

266 dest='rotate_to_enz', 

267 help=''' 

268Rotate waveforms to east-north-vertical (ENZ) coordinate system. The samples 

269in the input data must be properly aligned and the channel orientations must 

270by set in the station metadata (StationXML). Output channels are renamed with 

271last letter replaced by ``E``, ``N``, and ``Z`` respectively. 

272'''.strip()) 

273 

274 p.add_argument( 

275 '--out-path', 

276 dest='out_path', 

277 metavar='TEMPLATE', 

278 help=''' 

279Set output path to ``TEMPLATE``. Available placeholders are ``%%n``: network, 

280``%%s``: station, ``%%l``: location, ``%%c``: channel, ``%%b``: begin time, 

281``%%e``: end time, ``%%j``: julian day of year. The following additional 

282placeholders use the current processing window's begin and end times rather 

283than trace begin and end times (to suppress producing many small files for 

284gappy traces), ``%%(wmin_year)s``, ``%%(wmin_month)s``, ``%%(wmin_day)s``, 

285``%%(wmin)s``, ``%%(wmin_jday)s``, ``%%(wmax_year)s``, ``%%(wmax_month)s``, 

286``%%(wmax_day)s``, ``%%(wmax)s``, ``%%(wmax_jday)s``. Example: ``--out-path 

287'data/%%s/trace-%%s-%%c.mseed'`` 

288'''.strip()) 

289 

290 p.add_argument( 

291 '--out-sds-path', 

292 dest='out_sds_path', 

293 metavar='PATH', 

294 help=''' 

295Set output path to create an SDS archive 

296(https://www.seiscomp.de/seiscomp3/doc/applications/slarchive/SDS.html), rooted 

297at PATH. Implies ``--tinc 3600`` if not specified otherwise. Equivalent to 

298``--out-storage-path PATH --out-storage-scheme sds``. Example: 

299``--out-sds-path data/sds`` 

300'''.strip()) 

301 

302 p.add_argument( 

303 '--out-storage-path', 

304 dest='out_storage_path', 

305 metavar='PATH', 

306 help=''' 

307Create storage directory under PATH. The storage scheme can be set with 

308``--out-storage-scheme``. 

309'''.strip()) 

310 

311 p.add_argument( 

312 '--out-storage-scheme', 

313 dest='out_storage_scheme', 

314 default='default', 

315 choices=StorageSchemeChoice.choices, 

316 metavar='SCHEME', 

317 help=''' 

318Set storage scheme to produce when using ``--out-storage-path``. Choices: %s 

319'''.strip() % ldq(StorageSchemeChoice.choices)) 

320 

321 p.add_argument( 

322 '--out-format', 

323 dest='out_format', 

324 choices=io.allowed_formats('save'), 

325 metavar='FORMAT', 

326 help='Set output file format. Choices: %s' % io.allowed_formats( 

327 'save', 'cli_help', 'mseed')) 

328 

329 p.add_argument( 

330 '--out-data-type', 

331 dest='out_data_type', 

332 choices=OutputDataTypeChoice.choices, 

333 metavar='DTYPE', 

334 help='Set numerical data type. Choices: %s. The output file ' 

335 'format must support the given type. By default, the data ' 

336 'type is kept unchanged.' % ldq( 

337 OutputDataTypeChoice.choices)) 

338 

339 p.add_argument( 

340 '--out-mseed-record-length', 

341 dest='out_mseed_record_length', 

342 type=int, 

343 choices=io.mseed.VALID_RECORD_LENGTHS, 

344 metavar='INT', 

345 help='Set the Mini-SEED record length in bytes. Choices: %s. ' 

346 'Default is 4096 bytes, which is commonly used for archiving.' 

347 % ldq(str(b) for b in io.mseed.VALID_RECORD_LENGTHS)) 

348 

349 p.add_argument( 

350 '--out-mseed-steim', 

351 dest='out_mseed_steim', 

352 type=int, 

353 choices=(1, 2), 

354 metavar='INT', 

355 help='Set the Mini-SEED STEIM compression. Choices: ``1`` or ' 

356 '``2``. Default is STEIM-2. Note: STEIM-2 is limited to 30 ' 

357 'bit dynamic range.') 

358 

359 p.add_argument( 

360 '--out-meta-path', 

361 dest='out_meta_path', 

362 metavar='PATH', 

363 help='Set output path for station metadata (StationXML) export.') 

364 

365 p.add_argument( 

366 '--traversal', 

367 dest='traversal', 

368 metavar='GROUPING', 

369 choices=TraversalChoice.choices, 

370 help='By default the outermost processing loop is over time. ' 

371 'Add outer loop with given GROUPING. Choices: %s' 

372 % ldq(TraversalChoice.choices)) 

373 

374 p.add_argument( 

375 '--rename-network', 

376 dest='rename_network', 

377 metavar='REPLACEMENT', 

378 help=""" 

379Replace network code. REPLACEMENT can be a string for direct replacement, a 

380mapping for selective replacement, or a regular expression for tricky 

381replacements. Examples: Direct replacement: ```XX``` - set all network codes to 

382```XX```. Mapping: ```AA:XX,BB:YY``` - replace ```AA``` with ```XX``` and 

383```BB``` with ```YY```. Regular expression: ```/A(\\d)/X\\1/``` - replace 

384```A1``` with ```X1``` and ```A2``` with ```X2``` etc. 

385""".strip()) 

386 

387 p.add_argument( 

388 '--rename-station', 

389 dest='rename_station', 

390 metavar='REPLACEMENT', 

391 help='Replace station code. See ``--rename-network``.') 

392 

393 p.add_argument( 

394 '--rename-location', 

395 dest='rename_location', 

396 metavar='REPLACEMENT', 

397 help='Replace location code. See ``--rename-network``.') 

398 

399 p.add_argument( 

400 '--rename-channel', 

401 dest='rename_channel', 

402 metavar='REPLACEMENT', 

403 help='Replace channel code. See ``--rename-network``.') 

404 

405 p.add_argument( 

406 '--rename-extra', 

407 dest='rename_extra', 

408 metavar='REPLACEMENT', 

409 help='Replace extra code. See ``--rename-network``. Note: the ' 

410 '```extra``` code is not available in Mini-SEED.') 

411 

412 @classmethod 

413 def from_arguments(cls, args): 

414 kwargs = args.squirrel_query 

415 

416 rename = {} 

417 for (k, v) in [ 

418 ('network', args.rename_network), 

419 ('station', args.rename_station), 

420 ('location', args.rename_location), 

421 ('channel', args.rename_channel), 

422 ('extra', args.rename_extra)]: 

423 

424 if v is not None: 

425 rename[k] = parse_rename_rule_from_string(v) 

426 

427 if args.band: 

428 try: 

429 values = list(map(float, args.band.split(','))) 

430 if len(values) not in (2, 3, 4): 

431 raise ValueError() 

432 

433 if len(values) == 2: 

434 dset(kwargs, 'fmin fmax'.split(), values) 

435 elif len(values) == 3: 

436 dset(kwargs, 'fmin fmax fcut_factor'.split(), values) 

437 elif len(values) == 4: 

438 dset(kwargs, 'fmin_cut fmin fmax fmax_cut'.split(), values) 

439 

440 except ValueError: 

441 raise JackseisError( 

442 'Invalid argument to --band: %s' % args.band) from None 

443 

444 obj = cls( 

445 downsample=args.downsample, 

446 quantity=args.quantity, 

447 instrument_correction_mode=args.instrument_correction_mode, 

448 rotate_to_enz=args.rotate_to_enz, 

449 out_format=args.out_format, 

450 out_path=args.out_path, 

451 tinc=args.tinc, 

452 out_sds_path=args.out_sds_path, 

453 out_storage_path=args.out_storage_path, 

454 out_storage_scheme=args.out_storage_scheme, 

455 out_data_type=args.out_data_type, 

456 out_mseed_record_length=args.out_mseed_record_length, 

457 out_mseed_steim=args.out_mseed_steim, 

458 out_meta_path=args.out_meta_path, 

459 traversal=args.traversal, 

460 rename=rename, 

461 **kwargs) 

462 

463 obj.validate() 

464 return obj 

465 

466 def add_dataset(self, sq): 

467 if self.in_dataset is not None: 

468 sq.add_dataset(self.in_dataset) 

469 

470 if self.in_path is not None: 

471 ds = Dataset(sources=[LocalData(paths=[self.in_path])]) 

472 ds.set_basepath_from(self) 

473 sq.add_dataset(ds) 

474 

475 if self.in_paths: 

476 ds = Dataset(sources=[LocalData(paths=self.in_paths)]) 

477 ds.set_basepath_from(self) 

478 sq.add_dataset(ds) 

479 

480 def get_effective_rename_rules(self, chain): 

481 d = {} 

482 for k in ['network', 'station', 'location', 'channel']: 

483 v = chain.dget('rename', k) 

484 if isinstance(v, str): 

485 m = re.match(r'/([^/]+)/([^/]*)/', v) 

486 if m: 

487 try: 

488 v = (re.compile(m.group(1)), m.group(2)) 

489 except Exception: 

490 raise JackseisError( 

491 'Invalid replacement pattern: /%s/' % m.group(1)) 

492 

493 d[k] = v 

494 

495 return d 

496 

497 def get_effective_storage_scheme(self): 

498 nset = sum(x is not None for x in ( 

499 self.out_path, 

500 self.out_sds_path, 

501 self.out_storage_path)) 

502 

503 if nset > 1: 

504 raise JackseisError( 

505 'More than one out of [out_path, out_sds_path, ' 

506 'out_storage_path] set.') 

507 

508 if self.out_path: 

509 scheme = StorageScheme( 

510 name='custom', 

511 layouts=[StorageSchemeLayout( 

512 name='custom', 

513 path_template=self.expand_path(self.out_path))]) 

514 

515 elif self.out_sds_path: 

516 scheme = clone(get_storage_scheme('sds')) 

517 scheme.set_base_path(self.expand_path(self.out_sds_path)) 

518 

519 elif self.out_storage_path: 

520 scheme = clone(get_storage_scheme(self.out_storage_scheme)) 

521 scheme.set_base_path(self.expand_path(self.out_storage_path)) 

522 

523 else: 

524 scheme = None 

525 

526 return scheme 

527 

528 def get_effective_out_meta_path(self): 

529 if self.out_meta_path is not None: 

530 return self.expand_path(self.out_meta_path) 

531 else: 

532 return None 

533 

534 def do_rename(self, rules, tr): 

535 rename = {} 

536 for k in ['network', 'station', 'location', 'channel']: 

537 v = rules.get(k, None) 

538 if isinstance(v, str): 

539 rename[k] = v 

540 elif isinstance(v, dict): 

541 try: 

542 oldval = getattr(tr, k) 

543 rename[k] = v[oldval] 

544 except KeyError: 

545 raise ToolError( 

546 'No mapping defined for %s code "%s".' % (k, oldval)) 

547 

548 elif isinstance(v, tuple): 

549 pat, repl = v 

550 oldval = getattr(tr, k) 

551 newval, n = pat.subn(repl, oldval) 

552 if n: 

553 rename[k] = newval 

554 

555 tr.set_codes(**rename) 

556 

557 def convert( 

558 self, 

559 squirrel_factory=None, 

560 force=False, 

561 append=False, 

562 overrides=None, 

563 chain=None): 

564 

565 if chain is None: 

566 defaults = clone(g_defaults) 

567 defaults.set_basepath_from(self) 

568 chain = Chain(defaults) 

569 

570 chain = Chain(self, chain) 

571 

572 if self.parts: 

573 task = make_task('Jackseis parts') 

574 for part in task(self.parts): 

575 part.convert( 

576 squirrel_factory, 

577 force=force, 

578 append=append, 

579 overrides=overrides, 

580 chain=chain) 

581 

582 del task 

583 

584 else: 

585 if squirrel_factory is None: 

586 sq = Squirrel() 

587 else: 

588 sq = squirrel_factory() 

589 

590 if overrides: 

591 chain = Chain(overrides, chain) 

592 

593 chain.mcall('add_dataset', sq) 

594 

595 tmin = chain.get('tmin') 

596 tmax = chain.get('tmax') 

597 tinc = chain.get('tinc') 

598 codes = chain.get('codes') 

599 downsample = chain.get('downsample') 

600 rotate_to_enz = chain.get('rotate_to_enz') 

601 storage_scheme = chain.fcall('get_effective_storage_scheme') 

602 

603 out_format = chain.get('out_format') 

604 

605 if storage_scheme and storage_scheme.name in ('sds', 'default'): 

606 if tinc is None: 

607 logger.warning( 

608 'Setting processing time window to 1 hour to fill ' 

609 '"%s" storage.', 

610 storage_scheme.name) 

611 tinc = 3600.0 

612 

613 else: 

614 eps = 1e-6 

615 if (86400.0+eps) % tinc > 2.*eps: 

616 raise JackseisError( 

617 'Day length is not a multiple of the time ' 

618 'window (--tinc).') 

619 

620 if storage_scheme and storage_scheme.format is not None \ 

621 and out_format != storage_scheme.format: 

622 

623 logger.warning( 

624 'Setting file format to "%s" to generate storage with ' 

625 'scheme "%s"' % ( 

626 storage_scheme.format, storage_scheme.name)) 

627 

628 out_data_type = chain.get('out_data_type') 

629 

630 out_meta_path = chain.fcall('get_effective_out_meta_path') 

631 

632 if out_meta_path is not None: 

633 sx = sq.get_stationxml(codes=codes, tmin=tmin, tmax=tmax) 

634 util.ensuredirs(out_meta_path) 

635 sx.dump_xml(filename=out_meta_path) 

636 if storage_scheme is None: 

637 return 

638 

639 target_deltat = None 

640 if downsample is not None: 

641 target_deltat = 1.0 / float(downsample) 

642 

643 save_kwargs = {} 

644 if out_format == 'mseed': 

645 save_kwargs['record_length'] = chain.get( 

646 'out_mseed_record_length') 

647 save_kwargs['steim'] = chain.get( 

648 'out_mseed_steim') 

649 

650 traversal = chain.get('traversal') 

651 if traversal is not None: 

652 grouping = TraversalChoice.name_to_grouping[traversal] 

653 else: 

654 grouping = None 

655 

656 frequency_taper = self.get_effective_frequency_taper(chain) 

657 

658 if frequency_taper is not None: 

659 if frequency_taper[0] != 0.0: 

660 frequency_taper_tpad = 1.0 / frequency_taper[0] 

661 else: 

662 if frequency_taper[1] == 0.0: 

663 raise JackseisError( 

664 'Converter: fmin must be greater than zero.') 

665 

666 frequency_taper_tpad = 2.0 / frequency_taper[1] 

667 else: 

668 frequency_taper_tpad = 0.0 

669 

670 quantity = chain.get('quantity') 

671 ic_mode = chain.get('instrument_correction_mode') 

672 

673 do_transfer = \ 

674 quantity is not None or frequency_taper is not None 

675 

676 tpad = 0.0 

677 if target_deltat is not None: 

678 tpad += target_deltat * 50. 

679 

680 if do_transfer: 

681 tpad += frequency_taper_tpad 

682 

683 task = None 

684 rename_rules = self.get_effective_rename_rules(chain) 

685 for batch in sq.chopper_waveforms( 

686 tmin=tmin, tmax=tmax, tpad=tpad, tinc=tinc, 

687 codes=codes, 

688 snap_window=True, 

689 grouping=grouping): 

690 

691 if task is None: 

692 task = make_task( 

693 'Jackseis blocks', batch.n * batch.ngroups) 

694 

695 tlabel = '%s%s - %s' % ( 

696 'groups %i / %i: ' % (batch.igroup, batch.ngroups) 

697 if batch.ngroups > 1 else '', 

698 util.time_to_str(batch.tmin), 

699 util.time_to_str(batch.tmax)) 

700 

701 task.update(batch.i + batch.igroup * batch.n, tlabel) 

702 

703 twmin = batch.tmin 

704 twmax = batch.tmax 

705 

706 traces = batch.traces 

707 

708 if target_deltat is not None: 

709 downsampled_traces = [] 

710 for tr in traces: 

711 try: 

712 tr.downsample_to( 

713 target_deltat, snap=True, demean=False, 

714 allow_upsample_max=4) 

715 

716 downsampled_traces.append(tr) 

717 

718 except (trace.TraceTooShort, trace.NoData) as e: 

719 logger.warning(str(e)) 

720 

721 traces = downsampled_traces 

722 

723 if do_transfer: 

724 restituted_traces = [] 

725 for tr in traces: 

726 try: 

727 if quantity is not None: 

728 resp = sq.get_response(tr).get_effective( 

729 input_quantity=quantity, 

730 mode=ic_mode, 

731 gain_frequency=frequency_taper[1]) 

732 else: 

733 resp = None 

734 

735 restituted_traces.append(tr.transfer( 

736 frequency_taper_tpad, 

737 frequency_taper, 

738 transfer_function=resp, 

739 invert=True)) 

740 

741 except (trace.NoData, trace.TraceTooShort, 

742 SquirrelError) as e: 

743 logger.warning(str(e)) 

744 

745 traces = restituted_traces 

746 

747 if rotate_to_enz: 

748 sensors = sq.get_sensors( 

749 tmin=twmin, 

750 tmax=twmax, 

751 codes=list(set(tr.codes for tr in traces))) 

752 

753 rotated_traces = [] 

754 for sensor in sensors: 

755 sensor_traces = [ 

756 tr for tr in traces 

757 if tr.codes.matches(sensor.codes)] 

758 

759 rotated_traces.extend( 

760 sensor.project_to_enz(sensor_traces)) 

761 

762 traces = rotated_traces 

763 

764 for tr in traces: 

765 self.do_rename(rename_rules, tr) 

766 

767 if out_data_type: 

768 for tr in traces: 

769 tr.ydata = tr.ydata.astype( 

770 OutputDataTypeChoice.name_to_dtype[out_data_type]) 

771 

772 chopped_traces = [] 

773 for tr in traces: 

774 try: 

775 otr = tr.chop(twmin, twmax, inplace=False) 

776 chopped_traces.append(otr) 

777 except trace.NoData: 

778 pass 

779 

780 traces = chopped_traces 

781 if storage_scheme is not None: 

782 try: 

783 g_filenames_all.update(storage_scheme.save( 

784 traces, 

785 format=out_format, 

786 overwrite=force, 

787 append=True, 

788 check_append=True, 

789 check_append_hook=check_append_hook 

790 if not append else None, 

791 additional=dict( 

792 wmin_year=tts(twmin, format='%Y'), 

793 wmin_month=tts(twmin, format='%m'), 

794 wmin_day=tts(twmin, format='%d'), 

795 wmin_jday=tts(twmin, format='%j'), 

796 wmin=tts(twmin, format='%Y-%m-%d_%H-%M-%S'), 

797 wmax_year=tts(twmax, format='%Y'), 

798 wmax_month=tts(twmax, format='%m'), 

799 wmax_day=tts(twmax, format='%d'), 

800 wmax_jday=tts(twmax, format='%j'), 

801 wmax=tts(twmax, format='%Y-%m-%d_%H-%M-%S')), 

802 **save_kwargs)) 

803 

804 except io.FileSaveError as e: 

805 raise JackseisError(str(e)) 

806 

807 else: 

808 for tr in traces: 

809 print(tr.summary_stats) 

810 

811 if task: 

812 task.done() 

813 

814 

815g_defaults = Converter( 

816 out_mseed_record_length=4096, 

817 out_format='mseed', 

818 out_mseed_steim=2) 

819 

820 

821headline = 'Convert waveform archive data.' 

822 

823 

824def make_subparser(subparsers): 

825 return subparsers.add_parser( 

826 'jackseis', 

827 help=headline, 

828 description=headline) 

829 

830 

831def setup(parser): 

832 parser.add_squirrel_selection_arguments() 

833 

834 parser.add_argument( 

835 '--config', 

836 dest='config_path', 

837 metavar='NAME', 

838 help='File containing `jackseis.Converter` settings.') 

839 

840 parser.add_argument( 

841 '--dump-config', 

842 dest='dump_config', 

843 action='store_true', 

844 default=False, 

845 help=''' 

846Print configuration file snippet representing given command line arguments to 

847standard output and exit. Only command line options affecting the conversion 

848are included in the dump. Additional ``--config`` settings, data collection and 

849data query options are ignored. 

850'''.strip()) 

851 

852 parser.add_argument( 

853 '--force', 

854 dest='force', 

855 action='store_true', 

856 default=False, 

857 help='Force overwriting of existing files.') 

858 

859 parser.add_argument( 

860 '--append', 

861 dest='append', 

862 action='store_true', 

863 default=False, 

864 help='Append to existing files. This only works for mseed files. ' 

865 'Checks are preformed to ensure that appended traces have no ' 

866 'overlap with already existing traces.') 

867 

868 Converter.add_arguments(parser) 

869 

870 

871def run(parser, args): 

872 if args.dump_config: 

873 converter = Converter.from_arguments(args) 

874 print(converter) 

875 sys.exit(0) 

876 

877 if args.config_path: 

878 try: 

879 converters = load_all(filename=args.config_path) 

880 except Exception as e: 

881 raise ToolError(str(e)) 

882 

883 for converter in converters: 

884 if not isinstance(converter, Converter): 

885 raise ToolError( 

886 'Config file should only contain ' 

887 '`jackseis.Converter` objects.') 

888 

889 converter.set_basepath(op.dirname(args.config_path)) 

890 

891 else: 

892 converter = Converter() 

893 converter.set_basepath('.') 

894 converters = [converter] 

895 

896 cli_overrides = Converter.from_arguments(args) 

897 cli_overrides.set_basepath('.') 

898 

899 def squirrel_factory(): 

900 return args.make_squirrel(check_have_data=not args.config_path) 

901 

902 with progress.view(): 

903 task = make_task('Jackseis jobs') 

904 for converter in task(converters): 

905 

906 converter.convert( 

907 squirrel_factory=squirrel_factory, 

908 force=args.force, 

909 append=args.append, 

910 overrides=cli_overrides)