Skip to content
Closed
5 changes: 3 additions & 2 deletions .github/workflows/python-app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
- uses: actions/checkout@v2
- name: Set up Python 3
uses: actions/setup-python@v2
with:
python-version: '3.13'

- name: pip installation
# test pip installation first (much faster than conda)
Expand Down Expand Up @@ -53,5 +55,4 @@ jobs:
#- name: conda installation
# timeout-minutes: 60
# run: |
# bash tests/install_conda.bash

# bash tests/install_conda.bash
42 changes: 37 additions & 5 deletions mtuq/graphics/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

from matplotlib import pyplot
from matplotlib.font_manager import FontProperties
try:
from matplotlib.textpath import TextPath
except ImportError:
TextPath = None
from mtuq.graphics.beachball import plot_beachball, _plot_beachball_matplotlib
from mtuq.graphics._pygmt import exists_pygmt, plot_force_pygmt
from mtuq.graphics._matplotlib import plot_force_matplotlib
Expand Down Expand Up @@ -38,6 +42,8 @@ class HeaderStyle:
text_left_margin: float = HEADER_TEXT_LEFT_MARGIN
font_size: int = HEADER_TEXT_FONT_SIZE
text_vspace: float = HEADER_TEXT_VSPACE
text_right_margin: float = HEADER_TEXT_LEFT_MARGIN
min_font_size: int = 10


@dataclass
Expand Down Expand Up @@ -98,12 +104,15 @@ class TextBlock(HeaderBlock):
"""A text block for displaying formatted text in headers."""

def __init__(self, text: str, fontsize=HEADER_TEXT_FONT_SIZE, bold=False,
italic=False, vspace=HEADER_TEXT_VSPACE, **kwargs):
italic=False, vspace=HEADER_TEXT_VSPACE, auto_shrink=True,
min_fontsize=None, **kwargs):
self.text = text
self.fontsize = fontsize
self.bold = bold
self.italic = italic
self.vspace = vspace
self.auto_shrink = auto_shrink
self.min_fontsize = min_fontsize
self.kwargs = kwargs

def render(self, ax, info, px, py, *, height: float, width: float,
Expand All @@ -118,9 +127,30 @@ def render(self, ax, info, px, py, *, height: float, width: float,
fontsize = self.fontsize or style.font_size
vspace = self.vspace or style.text_vspace

text_value = self.text.format(**info.__dict__)

# Compute available width in axis units and shrink fonts if required
max_width = max(width - px - style.text_right_margin * height, 0.)
target_min = self.min_fontsize if self.min_fontsize is not None else style.min_font_size

if self.auto_shrink and max_width > 0 and TextPath is not None:
font_for_metrics = font.copy()
font_for_metrics.set_size(fontsize)
try:
text_path = TextPath((0, 0), text_value, prop=font_for_metrics)
text_width = text_path.get_extents().width / 72.0
except Exception:
text_width = 0.0

if text_width > max_width and text_width > 0.0:
scale = max_width / text_width
fontsize = max(target_min, fontsize * scale)

font.set_size(fontsize)

# Use attribute access for info container
ax.text(px, py, self.text.format(**info.__dict__), fontproperties=font,
fontsize=fontsize, **self.kwargs)
ax.text(px, py, text_value, fontproperties=font, fontsize=fontsize,
**self.kwargs)
return py - vspace


Expand Down Expand Up @@ -543,8 +573,9 @@ def create_moment_tensor_header(process_bw, process_sw, misfit_bw, misfit_sw,
best_misfit_bw, best_misfit_sw, model, solver, mt, lune_dict, origin,
data_bw=None, data_sw=None, mt_grid=None, event_name=None, **kwargs):
"""Create a complete moment tensor header"""
process_sw_supp = kwargs.pop('process_sw_supp', None)
header_info = prepare_moment_tensor_header_info(
origin, mt, lune_dict, process_bw, process_sw, kwargs.get('process_sw_supp', None),
origin, mt, lune_dict, process_bw, process_sw, process_sw_supp,
misfit_bw, misfit_sw, best_misfit_bw, best_misfit_sw, model, solver,
data_bw=data_bw, data_sw=data_sw, mt_grid=mt_grid, event_name=event_name, **kwargs)
header = build_moment_tensor_header(header_info)
Expand All @@ -555,8 +586,9 @@ def create_force_header(process_bw, process_sw, misfit_bw, misfit_sw,
best_misfit_bw, best_misfit_sw, model, solver, force, force_dict, origin,
data_bw=None, data_sw=None, force_grid=None, event_name=None, **kwargs):
"""Create a complete force header"""
process_sw_supp = kwargs.pop('process_sw_supp', None)
header_info = prepare_force_header_info(
origin, force, force_dict, process_bw, process_sw, kwargs.get('process_sw_supp', None),
origin, force, force_dict, process_bw, process_sw, process_sw_supp,
misfit_bw, misfit_sw, best_misfit_bw, best_misfit_sw, model, solver,
data_bw=data_bw, data_sw=data_sw, force_grid=force_grid, event_name=event_name, **kwargs)
header = build_force_header(header_info)
Expand Down
12 changes: 9 additions & 3 deletions mtuq/misfit/waveform/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def __call__(self, data, greens, sources, progress_handle=Null(),
normalize=normalize, ext='Cython')


def collect_attributes(self, data, greens, source, normalize=False):
def collect_attributes(self, data, greens, source, normalize=None):
""" Collects misfit, time shifts and other attributes corresponding to
each trace
"""
Expand All @@ -250,6 +250,9 @@ def collect_attributes(self, data, greens, source, normalize=False):
source, components=data.get_components(), stats=data.get_stats(),
mode='map', inplace=True)

if normalize is None:
normalize = self.normalize

# attaches attributes to synthetics
_ = level0.misfit(
data, greens, iterable(source), self.norm, self.time_shift_groups,
Expand All @@ -270,7 +273,7 @@ def collect_attributes(self, data, greens, source, normalize=False):
return deepcopy(attrs)


def collect_synthetics(self, data, greens, source, normalize=False, mode=2):
def collect_synthetics(self, data, greens, source, normalize=None, mode=2):
""" Collects synthetics with misfit, time shifts and other attributes attached
"""

Expand Down Expand Up @@ -310,11 +313,14 @@ def collect_synthetics(self, data, greens, source, normalize=False, mode=2):
source, components=components, stats=data.get_stats(),
mode='map', inplace=True)

if normalize is None:
normalize = self.normalize

# attaches attributes to synthetics
_ = level0.misfit(
data, greens, iterable(source), self.norm, self.time_shift_groups,
self.time_shift_min, self.time_shift_max, msg_handle=Null(),
normalize=False, set_attributes=True)
normalize=normalize, set_attributes=True)

return deepcopy(synthetics)

Expand Down
9 changes: 5 additions & 4 deletions mtuq/misfit/waveform/_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def calculate_norm_data(data, norm, components, apply_weights=True):
dt = stream[0].stats.delta

for _k in indices:
d = stream[_k].data
trace = stream[_k]
d = trace.data

if norm=='L1':
value = np.sum(np.abs(d))*dt
Expand All @@ -44,14 +45,14 @@ def calculate_norm_data(data, norm, components, apply_weights=True):
value = np.sum(d**2)*dt

elif norm=='hybrid':
value = np.sqrt(np.sum(r**2))*dt
value = np.sqrt(np.sum(d**2))*dt

# optionally, applies user-supplied weights attached during
# process_data()
if apply_weights:
try:
value *= d[_k].weight
except:
value *= trace.weight
except Exception:
pass

norm_data += value
Expand Down
8 changes: 7 additions & 1 deletion mtuq/misfit/waveform/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ def misfit(data, greens, sources, norm, time_shift_groups,

s[_k].attrs.norm = norm

s[_k].attrs.misfit = value
if normalize:
try:
s[_k].attrs.misfit = value / norm_data
except Exception:
s[_k].attrs.misfit = value
else:
s[_k].attrs.misfit = value

s[_k].attrs.idx_start = idx_start
s[_k].attrs.idx_stop = idx_stop
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description = "moment tensor (mt) uncertainty quantification (uq)"
authors = [
{ name = "Ryan Modrak" }
]
requires-python = ">=3"
requires-python = ">=3,<3.14"
keywords = ["seismology"]
classifiers = [
"Development Status :: 4 - Beta",
Expand Down