Skip to content

Commit a892072

Browse files
committed
New option --plot to save plots to files
1 parent 8a15da5 commit a892072

File tree

8 files changed

+98
-29
lines changed

8 files changed

+98
-29
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ to the latest version.
2424
frequency band specified in the config file
2525
- New option `--detailed` for `print_families`, to print more detailed
2626
information about each family, including the list of events
27+
- New option `--output` to save plots to files instead of showing them on
28+
screen
2729
- Config option `waveform_data_path` renamed to `sds_data_path`
2830
- New config option `event_data_path` to specify the path to a local directory
2931
with waveform files organized per event

requake/config/parse_arguments.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,23 @@ def parse_arguments(progname='requake'):
204204
'config file. Specify FREQ_MIN and FREQ_MAX in Hz.'
205205
)
206206
# ---
207+
# --- parent parser for saving plots to file instead of showing
208+
# them on screen
209+
output = argparse.ArgumentParser(add_help=False)
210+
output.add_argument(
211+
'-o', '--output', type=str, default='NO_OUTPUT', nargs='?',
212+
metavar='FILE_OR_FORMAT',
213+
help='Save the plot instead of displaying it. Provide a filename '
214+
'(e.g., "plot.png") to specify the output name and format, or '
215+
'just a format (e.g., "png", "pdf", "svg") to automatically '
216+
'generate a default filename. If omitted, the plot is saved as '
217+
'a PNG file with a default name.'
218+
)
219+
# ---
207220
# --- plot_pair
208221
plot_pair = subparser.add_parser(
209222
'plot_pair',
210-
parents=[traceid, freq_band],
223+
parents=[traceid, freq_band, output],
211224
help='plot traces for a given event pair'
212225
)
213226
plot_pair.add_argument('evid1')
@@ -309,37 +322,37 @@ def parse_arguments(progname='requake'):
309322
)
310323
# ---
311324
# --- print_families
312-
printfamilies = subparser.add_parser(
325+
print_families = subparser.add_parser(
313326
'print_families',
314327
parents=[
315328
longerthan, shorterthan, minevents, family_numbers, print_format
316329
],
317330
help='print families to screen'
318331
)
319-
printfamilies.add_argument(
332+
print_families.add_argument(
320333
'-d', '--detailed', action='store_true',
321334
help='print detailed information for each family, including a list '
322335
'of events'
323336
)
324337
# ---
325338
# --- plot_families
326-
plotfamilies = subparser.add_parser(
339+
plot_families = subparser.add_parser(
327340
'plot_families',
328341
parents=[
329342
longerthan, shorterthan, minevents, family_numbers,
330-
traceid, freq_band
343+
traceid, freq_band, output
331344
],
332345
help='plot traces for one ore more event families'
333346
)
334-
plotfamilies.add_argument(
347+
plot_families.add_argument(
335348
'-s', '--starttime', type=float, default=None,
336349
help='start time, in seconds relative to trace start, for the plot'
337350
)
338-
plotfamilies.add_argument(
351+
plot_families.add_argument(
339352
'-e', '--endtime', type=float, default=None,
340353
help='end time, in seconds relative to trace start, for the plot'
341354
)
342-
plotfamilies.add_argument(
355+
plot_families.add_argument(
343356
'-T', '--template', action='store_true',
344357
help='plot family members found with template scan'
345358
)
@@ -349,7 +362,7 @@ def parse_arguments(progname='requake'):
349362
'plot_timespans',
350363
parents=[
351364
longerthan, shorterthan, minevents, family_numbers, colorby,
352-
colormap, color_range
365+
colormap, color_range, output
353366
],
354367
help='plot family timespans'
355368
)
@@ -369,7 +382,7 @@ def parse_arguments(progname='requake'):
369382
'plot_cumulative',
370383
parents=[
371384
longerthan, shorterthan, minevents, family_numbers, colorby,
372-
colormap, color_range
385+
colormap, color_range, output
373386
],
374387
help='cumulative plot for one or more families'
375388
)
@@ -390,7 +403,7 @@ def parse_arguments(progname='requake'):
390403
'map_families',
391404
parents=[
392405
longerthan, shorterthan, minevents, family_numbers, colorby,
393-
colormap, color_range
406+
colormap, color_range, output
394407
],
395408
help='plot families on a map'
396409
)

requake/plot/map_families.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
from ..config import config, rq_exit
2020
from ..families import FamilyNotFoundError, read_selected_families
2121
from .plot_utils import (
22-
plot_title, hover_annotation, duration_string, family_colors, plot_colorbar
22+
plot_title, hover_annotation, duration_string, family_colors,
23+
plot_colorbar, save_or_show_plot
2324
)
2425
from .cached_tiler import CachedTiler
2526
from .map_tiles import (
@@ -155,4 +156,4 @@ def map_families():
155156
annot.set_visible(False)
156157
annot.hover_annotation = True
157158
fig.canvas.mpl_connect('motion_notify_event', hover_annotation)
158-
plt.show()
159+
save_or_show_plot(fig, 'family_map')

requake/plot/plot_cumulative.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from ..formulas import mag_to_slip_in_cm, mag_to_moment
2020
from .plot_utils import (
2121
format_time_axis, plot_title, hover_annotation, duration_string,
22-
family_colors, plot_colorbar
22+
family_colors, plot_colorbar, save_or_show_plot
2323
)
2424
logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
2525
# Reduce logging level for Matplotlib to avoid DEBUG messages
@@ -174,4 +174,4 @@ def plot_cumulative():
174174
annot.set_visible(False)
175175
annot.hover_annotation = True
176176
fig.canvas.mpl_connect('motion_notify_event', hover_annotation)
177-
plt.show()
177+
save_or_show_plot(fig, 'family_cumulative')

requake/plot/plot_families.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
get_family_aligned_waveforms_and_template
2424
)
2525
from ..waveforms import process_waveforms, NoWaveformError
26+
from .plot_utils import save_or_show_plot
2627
logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
2728
# Reduce logging level for Matplotlib to avoid DEBUG messages
2829
mpl_logger = logging.getLogger('matplotlib')
@@ -139,6 +140,10 @@ def _plot_family(family):
139140
f'{tr0.stats.freq_min:.1f}-{tr0.stats.freq_max:.1f} Hz'
140141
)
141142
ax.set_title(title, loc='right')
143+
figure_name = (
144+
f'family_{family.number}_{tr0.id}_'
145+
f'{tr0.stats.freq_min:.1f}-{tr0.stats.freq_max:.1f}Hz'
146+
)
142147

143148
def _zoom_lines(zoom_level):
144149
for line in tracelines:
@@ -206,6 +211,7 @@ def _keypress(event):
206211
_keypress.time_zoom_level = 1
207212
_keypress.pan_amount = 0
208213
fig.canvas.mpl_connect('key_press_event', _keypress)
214+
return fig, figure_name
209215

210216

211217
def plot_families():
@@ -226,13 +232,15 @@ def plot_families():
226232
'how to select specific families.')
227233
families = families[:20]
228234
for family in families:
229-
_plot_family(family)
230-
print('''
231-
Use left/right arrow keys to scroll backwards/forward in time.
232-
Use shift+left/shift+right to increase/decrease the time window.
233-
Use up/down arrow keys to increase/decrease trace amplitude.
234-
Press '0' to reset the view.
235-
Press 'a' to show/hide theoretical arrivals.
236-
Press 'q' to close a plot.
237-
''')
238-
plt.show()
235+
fig, figure_name = _plot_family(family)
236+
save_or_show_plot(fig, figure_name, show=False)
237+
if config.args.output == 'NO_OUTPUT':
238+
print('''
239+
Use left/right arrow keys to scroll backwards/forward in time.
240+
Use shift+left/shift+right to increase/decrease the time window.
241+
Use up/down arrow keys to increase/decrease trace amplitude.
242+
Press '0' to reset the view.
243+
Press 'a' to show/hide theoretical arrivals.
244+
Press 'q' to close a plot.
245+
''')
246+
plt.show()

requake/plot/plot_pair.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
WaveformPair, process_waveforms, align_pair,
1919
NoWaveformError
2020
)
21+
from .plot_utils import save_or_show_plot
2122
logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
2223
# Reduce logging level for Matplotlib to avoid DEBUG messages
2324
mpl_logger = logging.getLogger('matplotlib')
@@ -124,4 +125,7 @@ def plot_pair():
124125
_ax.yaxis.set_tick_params(which='minor', bottom=False)
125126
_ax.grid(True)
126127
_ax.legend(loc='upper right')
127-
plt.show()
128+
plot_file_basename = (
129+
f'{evid1}_{evid2}_{tr_id}_{freq_min:.1f}-{freq_max:.1f}Hz'
130+
)
131+
save_or_show_plot(fig, plot_file_basename)

requake/plot/plot_timespans.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from ..families import FamilyNotFoundError, read_selected_families
1818
from .plot_utils import (
1919
format_time_axis, plot_title, hover_annotation, duration_string,
20-
family_colors, plot_colorbar
20+
family_colors, plot_colorbar, save_or_show_plot
2121
)
2222
logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
2323
# Reduce logging level for Matplotlib to avoid DEBUG messages
@@ -138,4 +138,4 @@ def plot_timespans():
138138
annot.set_visible(False)
139139
annot.hover_annotation = True
140140
fig.canvas.mpl_connect('motion_notify_event', hover_annotation)
141-
plt.show()
141+
save_or_show_plot(fig, 'family_timespans')

requake/plot/plot_utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
"""
1212
import logging
1313
import contextlib
14-
import matplotlib.dates as mdates
1514
import numpy as np
15+
import matplotlib.pyplot as plt
16+
import matplotlib.dates as mdates
1617
from matplotlib import cm, colors
1718
from ..config import config
1819
from .colormaps import cmaps
@@ -349,3 +350,43 @@ def plot_colorbar(fig, ax, cmap, norm):
349350
format_time_axis(cbar.ax, which='yaxis', grid=False)
350351
with contextlib.suppress(AttributeError):
351352
cbar.ax.set_ylabel(cmap.label)
353+
354+
355+
def save_or_show_plot(fig, plot_file_basename, show=True):
356+
"""
357+
Save or show the plot.
358+
359+
:param fig: The Matplotlib figure to save or show.
360+
:param plot_file_basename: The base filename for the plot.
361+
Only used if the 'output' command line argument contains just the
362+
file format (e.g. 'png', 'pdf') or nothing is specified.
363+
:param show: Whether to show the plot or not.
364+
:type show: bool
365+
"""
366+
output = config.args.output
367+
if output == 'NO_OUTPUT':
368+
if show:
369+
plt.show()
370+
return
371+
if output is None:
372+
plot_format = 'png'
373+
elif output.lower() in ('pdf', 'svg', 'png', 'jpg', 'jpeg'):
374+
plot_format = output.lower()
375+
else:
376+
plot_format = None
377+
if plot_format is None:
378+
plot_file_name = output
379+
else:
380+
plot_file_name = f'{plot_file_basename}.{plot_format}'
381+
# sanitize plot filename
382+
plot_file_name = plot_file_name\
383+
.replace(' ', '_')\
384+
.replace(':', '-')\
385+
.replace('/', '-')
386+
fig.savefig(
387+
plot_file_name,
388+
dpi=300,
389+
bbox_inches='tight',
390+
)
391+
print(f'Plot saved as {plot_file_name}')
392+
plt.close(fig)

0 commit comments

Comments
 (0)