Scripting

You can write Python scripts that call the GMrecordsApp application to create high-level workflows. In the example below we create a Python script to run several subcommands to download and process ground motions recorded close to the epicenter of a magnitude 4.5 earthquake, and then export the results to CSV files and generate a report summarizing the results. The configuration and parameter files are in the docs/contents/tutorials directory.

In this tutorial, the commands are written to be executed in Jupyter Notebooks. Within a Jupyter Notebook, commands can be redirected to the terminal with the ! symbol and we make ue of this functionality here.

Local gmprocess configuration

In this example we specify parameters in the project configuration to produce a small dataset. We use only the FDSN fetcher and limit the station distance to 0.1 degrees. The configuration files are in the conf/scripting directory.

First, we list the available projects in the current directory.

!gmrecords projects --list
INFO 2026-06-04 18:01:30 | gmrecords._initialize: Logging level includes INFO.
INFO 2026-06-04 18:01:30 | gmrecords._initialize: PROJECTS_PATH: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/.gmprocess
INFO 2026-06-04 18:01:30 | projects.main: Running subcommand 'projects'

Project: cli-tutorial **Current Project**
	Conf Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/conf/cli
	Data Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/data/cli


Project: scripting-tutorial 
	Conf Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/conf/scripting
	Data Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/data/scripting

The PROJECTS_PATH shows the location of the projects configuration file where the information for the projects is stored. Second, we use the projects subcommand to select the project configuration scripting-tutorial.

!gmrecords projects --switch scripting-tutorial
INFO 2026-06-04 18:01:33 | gmrecords._initialize: Logging level includes INFO.
INFO 2026-06-04 18:01:33 | gmrecords._initialize: PROJECTS_PATH: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/.gmprocess
INFO 2026-06-04 18:01:33 | projects.main: Running subcommand 'projects'

Switched to project: 
Project: scripting-tutorial **Current Project**
	Conf Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/conf/scripting
	Data Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/data/scripting

Third, we create the directory to hold the data.

Note

We include the directory with the processing parameters for the project in the source code.

At this point we have an empty data/scripting directory. The conf/scripting directory has two files: fetchers/yml and user.yml. These configuration files hold parameters that override default values provided with the source code. See Configuration File for more information.

Download Data

In this example we will consider ground motions recorded for a magnitude 4.5 earthquake east of San Francisco, California. We have cached a snippet of the results of running gmrecords download --eventid nc73291880 in the tests/data/tutorials directory. Consequently, we simply copy the data from tests/data/tutorials/nc73291880 to data/scripting/nc73291880.

!mkdir -p data/scripting/.
!cp -r ../../../tests/data/tutorials/nc73291880 data/scripting/.

List Data

We now have earthquake rupture information and raw waveforms in the data/scripting directory.

!tree data/scripting
data/scripting
└── nc73291880
    ├── event.json
    └── raw
        ├── BK.BRIB.01.HNE__20191015T053312Z__20191015T054042Z.mseed
        ├── BK.BRIB.01.HNN__20191015T053312Z__20191015T054042Z.mseed
        ├── BK.BRIB.01.HNZ__20191015T053312Z__20191015T054042Z.mseed
        └── BK.BRIB.xml

3 directories, 5 files

Python Script

First we need to import the GMrecordsApp application and initial it

from gmprocess.apps.gmrecords import GMrecordsApp

app = GMrecordsApp()
app.load_subcommands()

Now, we need to create a dictionary with the arguments common to all subcommands. We must include arguments that normally are given default values by the command line argument parser.

args = {
    'debug': False,
    'quiet': False,
    'event_id': "nc73291880",
    'textfile': None,
    'overwrite': False,
    'num_processes': 0,
    'label': None,
    'datadir': None,
    'confdir': None,
    'resume': None,
}

And let’s make a convenience function to make calling the individual subcommands (i.e., “steps”) easier

def call_gmprocess_subcommand(subcommand):
    step_args = {
        'subcommand': subcommand,
        'func': app.classes[subcommand]['class'],
        'log': None,
    }
    args.update(step_args)
    app.main(**args)

Now we can easily call each subcommand

call_gmprocess_subcommand("assemble")

Hide code cell output

--------------------------------------------------------------------------------
Project: scripting-tutorial **Current Project**
	Conf Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/conf/scripting
	Data Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/data/scripting
--------------------------------------------------------------------------------
INFO 2026-06-04 18:01:36 | gmrecords._initialize: Logging level includes INFO.
INFO 2026-06-04 18:01:36 | gmrecords._initialize: PROJECTS_PATH: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/.gmprocess
INFO 2026-06-04 18:01:36 | assemble.main: Running subcommand 'assemble'
INFO 2026-06-04 18:01:36 | assemble.main: Number of events to assemble: 1
INFO 2026-06-04 18:01:36 | assemble.main: Assembling event nc73291880 (1 of 1)...
INFO 2026-06-04 18:01:36 | assemble._assemble_event: Calling assemble function for nc73291880...
INFO 2026-06-04 18:01:38 | assemble_utils.load_rupture: Rupture geometry file not found.
INFO 2026-06-04 18:01:39 | assemble_utils.assemble: 
1 StationStreams(s) in StreamCollection:
  3 StationTrace(s) in StationStream (passed):
    BK.BRIB.01.HNE | 2019-10-15T05:33:12.810000Z - 2019-10-15T05:40:42.800000Z | 100.0 Hz, 45000 samples (passed)
    BK.BRIB.01.HNN | 2019-10-15T05:33:12.810000Z - 2019-10-15T05:40:42.800000Z | 100.0 Hz, 45000 samples (passed)
    BK.BRIB.01.HNZ | 2019-10-15T05:33:12.810000Z - 2019-10-15T05:40:42.800000Z | 100.0 Hz, 45000 samples (passed)
INFO 2026-06-04 18:01:39 | stream_workspace.add_streams: Adding waveforms for BK.BRIB.01.HN
INFO 2026-06-04 18:01:39 | assemble._assemble_event: Done with assemble function for nc73291880...
INFO 2026-06-04 18:01:39 | base._summarize_files_created: The following files have been created:
INFO 2026-06-04 18:01:39 | base._summarize_files_created: File type: Workspace
INFO 2026-06-04 18:01:39 | base._summarize_files_created: 	/builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/data/scripting/nc73291880/workspace.h5
call_gmprocess_subcommand("process_waveforms")

Hide code cell output

--------------------------------------------------------------------------------
Project: scripting-tutorial **Current Project**
	Conf Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/conf/scripting
	Data Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/data/scripting
--------------------------------------------------------------------------------
INFO 2026-06-04 18:01:39 | gmrecords._initialize: Logging level includes INFO.
INFO 2026-06-04 18:01:39 | gmrecords._initialize: PROJECTS_PATH: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/.gmprocess
INFO 2026-06-04 18:01:39 | process_waveforms.main: Running subcommand 'process_waveforms'
INFO 2026-06-04 18:01:39 | process_waveforms.main: Number of events to process: 1
INFO 2026-06-04 18:01:39 | process_waveforms.main: Processing tag: default
INFO 2026-06-04 18:01:39 | process_waveforms.main: Processing waveforms for event nc73291880 (1 of 1)...
INFO 2026-06-04 18:01:39 | process_waveforms._process_event: Processing 'unprocessed' streams for event nc73291880...
INFO 2026-06-04 18:01:40 | processing.process_streams: Stream: BK.BRIB.01.HN
INFO 2026-06-04 18:01:51 | stream_workspace.add_streams: Adding waveforms for BK.BRIB.01.HN
CRITICAL 2026-06-04 18:01:51 | gmrecords.main: Could not serialize -40024 of type <class 'numpy.int32'>.
Traceback (most recent call last):
  File "/builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/apps/gmrecords.py", line 123, in main
    self.args.func().main(self)
  File "/builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/subcommands/process_waveforms.py", line 58, in main
    self._process_event(event_id)
  File "/builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/subcommands/process_waveforms.py", line 139, in _process_event
    self.workspace.add_streams(
  File "/builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/io/asdf/stream_workspace.py", line 489, in add_streams
    self.insert_aux(dict_to_str(jdict), dtype, trace_path, overwrite)
                    ^^^^^^^^^^^^^^^^^^
  File "/builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/io/asdf/stream_workspace.py", line 1028, in dict_to_str
    return json.dumps(indict, default=_serializer)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
          ^^^^^^^^^^^
  File "/usr/lib/python3.12/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/encoder.py", line 258, in iterencode
    return _iterencode(o, 0)
           ^^^^^^^^^^^^^^^^^
  File "/builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/io/asdf/stream_workspace.py", line 1026, in _serializer
    raise ValueError(f"Could not serialize {value} of type {type(value)}.")
ValueError: Could not serialize -40024 of type <class 'numpy.int32'>.


Args: 
    debug: False
    quiet: False
    event_id: nc73291880
    textfile: None
    overwrite: False
    num_processes: 0
    label: None
    datadir: None
    confdir: None
    resume: None
    subcommand: process_waveforms
    func: <class 'gmprocess.subcommands.process_waveforms.ProcessWaveformsModule'>
    log: None
    reprocess: False
Project Name: scripting-tutorial
Conf Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/conf/scripting
Data Path: /builds/ghsc/esi/groundmotion-processing/docs/contents/tutorials/data/scripting
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[9], line 1
----> 1 call_gmprocess_subcommand("process_waveforms")

Cell In[7], line 8, in call_gmprocess_subcommand(subcommand)
      4         'func': app.classes[subcommand]['class'],
      5         'log': None,
      6     }
      7     args.update(step_args)
----> 8     app.main(**args)

File /builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/apps/gmrecords.py:139, in GMrecordsApp.main(self, **kwargs)
    137 msg = msg + f"Data Path: {self.data_path}\n"
    138 logging.critical(msg)
--> 139 raise e

File /builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/apps/gmrecords.py:123, in GMrecordsApp.main(self, **kwargs)
    117 # -----------------------------------------------------------------
    118 # This calls the init method of the subcommand that was specified
    119 # at the command line and hands off the GmpApp object ("self") as
    120 # the only argument to func.
    121 # -----------------------------------------------------------------
    122 try:
--> 123     self.args.func().main(self)
    124 except FileExistsError as e:
    125     print(e)

File /builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/subcommands/process_waveforms.py:58, in ProcessWaveformsModule.main(self, gmrecords)
     53 for ievent, event_id in enumerate(event_ids):
     54     logging.info(
     55         f"Processing waveforms for event {event_id} "
     56         f"({1+ievent} of {len(event_ids)})..."
     57     )
---> 58     self._process_event(event_id)
     60 self._summarize_files_created()

File /builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/subcommands/process_waveforms.py:139, in ProcessWaveformsModule._process_event(self, event_id)
    136     overwrite = True
    138 for processed_stream in processed_streams:
--> 139     self.workspace.add_streams(
    140         event,
    141         processed_stream,
    142         label=self.process_tag,
    143         gmprocess_version=self.gmrecords.gmprocess_version,
    144         overwrite=overwrite,
    145     )
    147 self.close_workspace()
    148 return event_id

File /builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/io/asdf/stream_workspace.py:489, in StreamWorkspace.add_streams(self, event, streams, label, gmprocess_version, overwrite)
    487 if len(jdict):
    488     dtype = "TraceProcessingParameters"
--> 489     self.insert_aux(dict_to_str(jdict), dtype, trace_path, overwrite)
    491 # Some processing data is computationally intensive to
    492 # compute, so we store it in the 'Cache' group.
    493 for specname in trace.get_cached_names():

File /builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/io/asdf/stream_workspace.py:1028, in dict_to_str(indict)
   1025         return value.decode()
   1026     raise ValueError(f"Could not serialize {value} of type {type(value)}.")
-> 1028 return json.dumps(indict, default=_serializer)

File /usr/lib/python3.12/json/__init__.py:238, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    232 if cls is None:
    233     cls = JSONEncoder
    234 return cls(
    235     skipkeys=skipkeys, ensure_ascii=ensure_ascii,
    236     check_circular=check_circular, allow_nan=allow_nan, indent=indent,
    237     separators=separators, default=default, sort_keys=sort_keys,
--> 238     **kw).encode(obj)

File /usr/lib/python3.12/json/encoder.py:200, in JSONEncoder.encode(self, o)
    196         return encode_basestring(o)
    197 # This doesn't pass the iterator directly to ''.join() because the
    198 # exceptions aren't as detailed.  The list call should be roughly
    199 # equivalent to the PySequence_Fast that ''.join() would do.
--> 200 chunks = self.iterencode(o, _one_shot=True)
    201 if not isinstance(chunks, (list, tuple)):
    202     chunks = list(chunks)

File /usr/lib/python3.12/json/encoder.py:258, in JSONEncoder.iterencode(self, o, _one_shot)
    253 else:
    254     _iterencode = _make_iterencode(
    255         markers, self.default, _encoder, self.indent, floatstr,
    256         self.key_separator, self.item_separator, self.sort_keys,
    257         self.skipkeys, _one_shot)
--> 258 return _iterencode(o, 0)

File /builds/ghsc/esi/groundmotion-processing/venv/lib/python3.12/site-packages/gmprocess/io/asdf/stream_workspace.py:1026, in dict_to_str.<locals>._serializer(value)
   1024 if isinstance(value, bytes):
   1025     return value.decode()
-> 1026 raise ValueError(f"Could not serialize {value} of type {type(value)}.")

ValueError: Could not serialize -40024 of type <class 'numpy.int32'>.
call_gmprocess_subcommand("compute_station_metrics")
call_gmprocess_subcommand("compute_waveform_metrics")
call_gmprocess_subcommand("export_metric_tables")
call_gmprocess_subcommand("generate_station_maps")

This will produce CSV files with the waveform metrics in the data/scripting directory.

!ls -1 data/scripting/*.csv

The station map will be in the data/scripting/nc73291880 directory.

!ls -1 data/scripting/nc73291880/*.html