Skip to content

Commit

Permalink
fix: computed slopes in db/oct
Browse files Browse the repository at this point in the history
  • Loading branch information
pierreaubert committed Dec 17, 2024
1 parent e1cadd1 commit 8245d5d
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 33 deletions.
2 changes: 1 addition & 1 deletion generate_radar.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def print_radar(meta_data, scale, speaker_data):
for gd in graph_data:
fig.add_trace(go.Scatterpolar(gd))
fig.update_layout(layout)
write_multiformat(fig, filename, False);
write_multiformat(fig, filename, False)


def main(args):
Expand Down
16 changes: 15 additions & 1 deletion src/spinorama/compute_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
from scipy import stats

from spinorama import logger
from spinorama.constant_paths import (
SLOPE_MIN_FREQ,
SLOPE_MAX_FREQ,
)
from spinorama.load_misc import sort_angles
from spinorama.compute_scores import octave

Expand Down Expand Up @@ -449,5 +453,15 @@ def compute_statistics(
hist = np.histogram(hist_dist, bins=[0, 0.5, 1, 1.5, 2, 2.5, 3, 5], density=False)
# 3 = math.log10(20000)-math.log10(20)
# 11 octaves between 20Hz and 20kHz
db_per_octave = result.slope * 3.0 / 11.0
slope_min_freq = max(SLOPE_MIN_FREQ, data_frame.Freq.iat[0])
slope_max_freq = min(SLOPE_MAX_FREQ, data_frame.Freq.iat[-1])
slopes_minmax = data_frame.loc[
(data_frame.Freq > slope_min_freq) & (data_frame.Freq < slope_max_freq)
]
slopes_spl = slopes_minmax[measurement]
first_freq = slopes_minmax.Freq.iat[0]
last_freq = slopes_minmax.Freq.iat[-1]
first_spl = slopes_spl.iat[0]
last_spl = slopes_spl.iat[-1]
db_per_octave = (last_spl - first_spl) / math.log2(last_freq / first_freq)
return db_per_octave, hist, np.max(hist_dist)
2 changes: 2 additions & 0 deletions src/spinorama/pict.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ def write_multiformat(chart, filename, force):
filename = filename.replace("_large", "")
webp = "{}.webp".format(filename[:-4])
if not pathlib.Path(webp).is_file() or force:
print(webp)
pict.convert("webp").save(filename=webp)
pict.compression_quality = 75
jpg = "{}.jpg".format(filename[:-4])
if not pathlib.Path(jpg).is_file() or force:
print(jpg)
pict.convert("jpg").save(filename=jpg)
118 changes: 89 additions & 29 deletions src/spinorama/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@

pio.templates.default = "plotly_white"

FONT_SIZE_H1 = 18
FONT_SIZE_H2 = 16
FONT_SIZE_H3 = 14
FONT_SIZE_H4 = 12
FONT_SIZE_H1 = 16
FONT_SIZE_H2 = 14
FONT_SIZE_H3 = 12
FONT_SIZE_H4 = 11
FONT_SIZE_H5 = 10
FONT_SIZE_H6 = 9

Expand Down Expand Up @@ -506,6 +506,13 @@ def plot_spinorama(spin, params):
def plot_spinorama_normalized_traces(spin, params):
layout = params.get("layout", "")
traces = []
lines = []
slope_min_freq = max(SLOPE_MIN_FREQ, spin.Freq.iat[0])
slope_max_freq = min(SLOPE_MAX_FREQ, spin.Freq.iat[-1])
restricted_freq = spin.loc[(spin.Freq >= slope_min_freq) & (spin.Freq <= slope_max_freq)]
first_freq = restricted_freq.Freq.iat[0]
last_freq = restricted_freq.Freq.iat[-1]
slope_octave = math.log2(last_freq / first_freq)
for measurement in (
"On Axis",
"Listening Window",
Expand All @@ -521,26 +528,41 @@ def plot_spinorama_normalized_traces(spin, params):
name=label_short.get(measurement, measurement),
hovertemplate="Freq: %{x:.0f}Hz<br>SPL: %{y:.1f}dB<br>",
)
restricted_freq = spin.loc[(spin.Freq >= SLOPE_MIN_FREQ) & (spin.Freq <= SLOPE_MAX_FREQ)]
slope, intercept, _, _, _ = stats.linregress(
x=np.log10(restricted_freq["Freq"]), y=restricted_freq[measurement]
)
# line = go.Scatter(
# x=restricted_freq,
# y=[slope * math.log10(f) + intercept for f in restricted_freq.Freq],
# marker_color=UNIFORM_COLORS.get(measurement, "black")
# )
# traces.append(line)
slope /= 6 # range covers ~6 octaves
res = stats.linregress(x=np.log10(restricted_freq["Freq"]), y=restricted_freq[measurement])
slope_dboct = res.slope * (math.log10(last_freq) - math.log10(first_freq)) / slope_octave
first_spl = res.intercept + res.slope * math.log10(first_freq)
last_spl = res.intercept + res.slope * math.log10(last_freq)
if measurement in ("Sound Power", "Early Reflections"):
print(
"Freq [{}, {}]Hz SPL [{}, {}] regression slope {} slope {} db/oct intercept {}".format(
first_freq,
last_freq,
first_spl,
last_spl,
res.slope,
slope_dboct,
res.intercept,
)
)
line_computed = go.Scatter(
x=[first_freq, last_freq],
y=[first_spl, last_spl],
line=dict(width=2, dash="dash", color=UNIFORM_COLORS[measurement]),
opacity=1,
legend="legend2",
name="{:20s} slope {:+4.1f}dB/oct".format(measurement, slope_dboct),
)
lines.append(line_computed)
if layout == "compact":
trace.name = "{} {:3.1f}dB/oct".format(label_short.get(measurement, measurement), slope)
trace.name = label_short.get(measurement, measurement)
else:
trace.name = "{} slope {:3.1f}dB/oct".format(measurement, slope)
trace.name = measurement
trace.legendgroup = "measurements"
trace.legendgrouptitle = {"text": "Measurements"}
traces.append(trace)

traces_di = []
lines_di = []
for measurement in ("Early Reflections DI", "Sound Power DI"):
if measurement not in spin:
continue
Expand All @@ -550,33 +572,60 @@ def plot_spinorama_normalized_traces(spin, params):
marker_color=UNIFORM_COLORS.get(measurement, "black"),
hovertemplate="Freq: %{x:.0f}Hz<br>SPL: %{y:.1f}dB<br>",
)
restricted_freq = spin.loc[(spin.Freq >= SLOPE_MIN_FREQ) & (spin.Freq <= SLOPE_MAX_FREQ)]
slope, intercept, _, _, _ = stats.linregress(
x=np.log10(restricted_freq["Freq"]), y=restricted_freq[measurement]
res = stats.linregress(x=np.log10(restricted_freq["Freq"]), y=restricted_freq[measurement])
slope_dboct = res.slope * (math.log10(last_freq) - math.log10(first_freq)) / slope_octave
first_spl = res.intercept + res.slope * math.log10(first_freq)
last_spl = res.intercept + res.slope * math.log10(last_freq)
line_computed = go.Scatter(
x=[first_freq, last_freq],
y=[first_spl, last_spl],
line=dict(width=2, dash="dash", color=UNIFORM_COLORS[measurement]),
opacity=1,
name="{:20s} slope {:+4.1f}dB/oct".format(measurement, slope_dboct),
showlegend=True,
legend="legend2",
)
slope /= 6 # range covers ~6 octaves
lines_di.append(line_computed)
if measurement == "Sound Power DI":
# https://kimmosaunisto.net/Misc/speaker_review_feedback.pdf
spl_min = res.intercept + 0.85 * slope_octave * math.log10(last_freq / first_freq)
spl_max = res.intercept + 1.10 * slope_octave * math.log10(last_freq / first_freq)
x = [first_freq, last_freq, last_freq, first_freq]
y = [first_spl, spl_min, spl_max, first_spl]
lines_di.append(
go.Scatter(
x=x,
y=y,
fill="toself",
opacity=0.4,
legend="legend2",
name="recommended SPDI zone",
)
)
if layout == "compact":
trace.name = "{} {:3.1f}dB/oct".format(label_short.get(measurement, measurement), slope)
trace.name = label_short.get(measurement, measurement)
else:
trace.name = "{} slope {:3.1f}dB/oct".format(measurement, slope)
trace.name = measurement
trace.legendgroup = "directivity"
trace.legendgrouptitle = {"text": "Directivity"}
traces_di.append(trace)
return traces, traces_di
return traces, traces_di, lines, lines_di


def plot_spinorama_normalized(spin, params):
print(spin.keys())
fig = make_subplots(specs=[[{"secondary_y": True}]])
t_max = 0
traces, traces_di = plot_spinorama_normalized_traces(spin, params)
traces, traces_di, lines, lines_di = plot_spinorama_normalized_traces(spin, params)
for t in traces:
t_max = max(t_max, np.max(t.y[np.where(t.x < 20000)]))
fig.add_trace(t, secondary_y=False)

t_max = 5 + int(t_max / 5) * 5
t_min = t_max - 50

for l in lines:
fig.add_trace(l, secondary_y=False)

di_max = 0
for t in traces_di:
di_max = max(di_max, np.max(t.y[np.where(t.x < 20000)]))
Expand All @@ -585,11 +634,23 @@ def plot_spinorama_normalized(spin, params):
di_max = 35 + int(di_max / 5) * 5
di_min = di_max - 50

for l in lines_di:
fig.add_trace(l, secondary_y=True)

fig.update_xaxes(generate_xaxis())
fig.update_yaxes(generate_yaxis_spl(t_min, t_max, 5))
fig.update_yaxes(generate_yaxis_di(di_min, di_max, 5), secondary_y=True)

fig.update_layout(common_layout(params))
fig.update_layout(
legend2=dict(
orientation="v",
yanchor="middle",
y=0.5,
xanchor="left",
x=0.05,
)
)
return fig


Expand Down Expand Up @@ -936,11 +997,10 @@ def plot_radar_freq(anglelist, freqlist, df):
db_mean = dfu.loc[(dfu.Freq > 900) & (dfu.Freq < 1100)]["On Axis"].mean()
freq = dfu.Freq
dfu = dfu.drop("Freq", axis=1)
db_min = np.min(dfu.min(axis=0).values)
db_max = np.max(dfu.max(axis=0).values)
max(abs(db_max), abs(db_min))
# if df is normalized then 0 will be at the center of the radar which is not what
# we want. Let's shift the whole graph up.
# db_min = np.min(dfu.min(axis=0).values)
# db_max = np.max(dfu.max(axis=0).values)
# if db_mean < 45:
# dfu += db_scale
# print(db_min, db_max, db_mean, db_scale)
Expand Down
2 changes: 1 addition & 1 deletion src/spinorama/speaker_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def print_graphs(
("SPL Horizontal Normalized", display_spl_horizontal_normalized),
("SPL Vertical Normalized", display_spl_vertical_normalized),
):
logger.debug("%s %s %s %s", speaker, version, origin, ",".join(list(df_speaker.keys())))
# logger.debug("%s %s %s %s", speaker, version, origin, ",".join(list(df_speaker.keys())))
graph = op_call(df_speaker, graph_params)
if graph is None:
logger.debug("display %s failed for %s %s %s", op_title, speaker, version, origin)
Expand Down
2 changes: 1 addition & 1 deletion src/website/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def float2str(context, f):
return f.split(".")[0]


def eqtype2str(context, eq_type : str) -> str:
def eqtype2str(context, eq_type: str) -> str:
infos = {
"0": "LP",
"1": "HP",
Expand Down

0 comments on commit 8245d5d

Please sign in to comment.