曲线图表头加齿轮,绘图设置对话框里可以直接调滤波 - What: ui/icons.py 新增 settings(齿轮)图标(八个齿 + 圆环,颜色取自主题 token,与另外三个图标同一套画法);force_plots.py 表头新增 btn_settings 与 plot_settings_requested 信号(放在缩放按钮之后、单位芯片之前),换主题时一并重绘图标,retranslate 里补 tooltip/可访问名;plot_settings_dialog.py 增加"滤波"分区:预设下拉(off/low_latency/balanced/low_noise + 自定义,显示的就是写进 sensor.json 的那个名字)、光流 EMA α(0.05–1.00,1.00 = 关)、中值窗口(1–31,1 = 关)、低通截止(0–60 Hz,0 = 关)、低通阶数(1–6)、采样率("自动跟随帧率"复选框 + 固定值)、"同时显示未滤波曲线";选预设即写入该预设的数值,手改任一数值自动切到"自定义",选中的预设名会随 filter_values() 一起带出(preset 字段),"_reset_defaults" 回到"什么都不滤";对话框底部按当前数值给提示:光流 EMA 打开时提示"这是测量设置、两次录制不可比"、全关时提示"当前不滤波"、录制中显示"录制中不能更改滤波"并禁用整个分区;截止频率达到或超过奈奎斯特(帧率一半)时用 dialog.filter_nyquist 明确说出"设备不会滤",其中 cutoff/nyquist/rate 三个数字由 FilterSettings.nyquist_issue() 提供(problems() 仍返回英文串供日志与测试用,判断条件只有一处)。filters.py 新增 FALLBACK_PRESETS 与 available_presets()(SDK 能导入就用 SDK 的 FILTER_PRESETS,导入不了才用这份文档副本,测试保证两者逐值一致)。app_window.py:把 plot_settings_requested 接到 _on_plot_settings,打开对话框时传入当前 _filter_settings()、filters_locked(正在跑或正在录)、以及 _device_sample_rate()(实测帧率优先,否则 30 fps,用于奈奎斯特检查);接受后把 filter_values().as_dict() 逐键写成 plot_* 偏好(与 _filter_settings() 读回的是同一组键),未在采集时立刻 set_filter_settings() 交给两个 worker,并在状态栏回报"已保存:{describe}(下次采集生效)"。两个 locale 文件补齐 21+3 个新键,中英各 343 键一一对应。tests/test_ui_smoke.py 新增 4 项:齿轮确实触发设置信号、预设/自定义切换与数值一致、录制中分区被禁用且给出理由、对话框写的键与 _filter_settings 读回的键是同一组。 - Why: 用户要求"建议曲线图增加一个齿轮的配置按键,可以调节曲线图的上限和滤波等"。此前滤波只能靠手改 preferences.json 的 plot_* 键(上一轮留下的口子),而 app 默认已改成"什么都不滤"——没有这个入口,等于把默认变成了唯一选择。 - Verification: studio 全量 89 passed(原 85 + 新增 4);shared 全量 158 passed;ui_check 554 项检查 0 失败;.repro/locale_check.py 扫描 64 个源文件里 287 个 t("…") 字面键,两个 locale 全部命中且键集合完全相同(343/343);离屏实测对话框:预设 balanced→flowEMA(0.5)+median(3)、改中值→自定义、选 low_noise→带低通、30 Hz 采样率下填 30 Hz 截止→弹出奈奎斯特警告、恢复默认→off/不滤波。 - Not in this change: CSV 的 17 列与未滤波第二 buffer(SessionStore/记录格式版本)、回放界面显示滤波前曲线、标定面板显示 filters 块、发布说明里"默认不再滤波"的措辞。
This commit is contained in:
@@ -2105,6 +2105,73 @@ def test_the_status_bar_is_a_notice_line_not_a_state_line(make_window):
|
||||
_finish(window, stuck)
|
||||
|
||||
|
||||
# ------------------------------------------------------- plot settings dialog
|
||||
def test_the_chart_gear_opens_the_settings_dialog(qt_app, make_window):
|
||||
"""The gear is the one place the filters can be changed from the UI."""
|
||||
window = make_window()
|
||||
captured = []
|
||||
window._on_plot_settings = lambda: captured.append(True)
|
||||
window.force_plots.plot_settings_requested.disconnect()
|
||||
window.force_plots.plot_settings_requested.connect(window._on_plot_settings)
|
||||
window.force_plots.btn_settings.click()
|
||||
assert captured == [True], "the gear did not ask for the settings dialog"
|
||||
assert window.force_plots.btn_settings.toolTip()
|
||||
window.close()
|
||||
|
||||
|
||||
def test_the_filter_section_is_a_preset_and_a_custom_shape(qt_app):
|
||||
"""A preset means its values; editing one turns the choice into "custom"."""
|
||||
from orisys_app_shared.force.filters import FilterSettings
|
||||
from orisys_studio.ui.plot_settings_dialog import PlotSettingsDialog
|
||||
|
||||
dialog = PlotSettingsDialog(filters=FilterSettings(preset="balanced"))
|
||||
assert dialog.selected_preset() == "balanced"
|
||||
assert dialog.filter_values().describe() == "flowEMA(0.5) + median(3)"
|
||||
|
||||
dialog.median_spin.setValue(7)
|
||||
assert dialog.selected_preset() is None, "an edited preset must stop claiming the name"
|
||||
assert dialog.filter_values().describe() == "flowEMA(0.5) + median(7)"
|
||||
|
||||
dialog.preset_combo.setCurrentIndex(3) # low_noise
|
||||
assert dialog.filter_values().describe() == "flowEMA(0.3) + median(3) + lowpass(10Hz,order2)"
|
||||
|
||||
# A cutoff the device would drop is stated before the session starts.
|
||||
dialog.lowpass_spin.setValue(30.0)
|
||||
assert "Nyquist" in dialog.filter_warning.text() or "奈奎斯特" in dialog.filter_warning.text()
|
||||
|
||||
dialog._reset_defaults()
|
||||
assert dialog.selected_preset() == "off"
|
||||
assert dialog.filter_values().active() is False
|
||||
assert dialog.filter_note.text(), "the dialog should say that nothing is filtered"
|
||||
|
||||
|
||||
def test_the_filter_controls_are_locked_while_a_session_runs(qt_app):
|
||||
from orisys_app_shared.force.filters import FilterSettings
|
||||
from orisys_studio.ui.plot_settings_dialog import PlotSettingsDialog
|
||||
|
||||
dialog = PlotSettingsDialog(filters=FilterSettings(), filters_locked=True)
|
||||
assert not dialog.filters_group.isEnabled()
|
||||
assert dialog.filter_note.text(), "a locked dialog has to say why"
|
||||
# Reading the values still works: the operator can see what is running.
|
||||
assert dialog.filter_values().active() is False
|
||||
|
||||
|
||||
def test_the_dialog_writes_the_keys_the_session_reads_back(qt_app):
|
||||
"""The dialog's own vocabulary must be what ``_filter_settings`` reads."""
|
||||
from orisys_app_shared.force.filters import FilterSettings, settings_from_mapping
|
||||
|
||||
settings = FilterSettings(
|
||||
flow_ema_alpha=0.5, median_window=3, lowpass_hz=10.0, show_unfiltered=True,
|
||||
preset="low_noise",
|
||||
)
|
||||
stored = {f"plot_{key}": value for key, value in settings.as_dict().items()}
|
||||
read_back = settings_from_mapping(
|
||||
{key.removeprefix("plot_"): value for key, value in stored.items()}
|
||||
)
|
||||
assert read_back == settings.normalised()
|
||||
assert read_back.describe() == "flowEMA(0.5) + median(3) + lowpass(10Hz,order2)"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user