Coverage for graphica/gui/datasets/operations/peaks.py: 97.3%
59 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""ピーク検出と、ピーク位置への自動ラベルの本体。"""
2import logging
4import numpy as np
5import pandas as pd
7from graphica.core.analysis import assign_peak_label_levels, calculate_peak_quantification, calculate_peaks
8from graphica.core.dataset import Dataset
9from graphica.gui.datasets.operations.runner import Operation
10from graphica.gui.dialogs import PeakSettingsDialog, ResultDialog
12logger = logging.getLogger(__name__)
14_DEFAULT_PEAK_TYPE = "上に凸 (Peaks)"
15PEAK_RESULT_WINDOW = "peaks"
18def _ask_settings(op, dataset):
19 """点数の確認と設定ダイアログ。"""
20 if len(dataset.x_data) < 3:
21 op.stop_with_warning("データ点数が少なすぎます (最低3点必要)。")
22 settings = PeakSettingsDialog.get_peak_settings(op.parent)
23 if settings is None:
24 op.stop()
25 return settings
28def _detect(op, compute):
29 # 検出の失敗は想定外のものも含めて知らせる(文字の X 列などで計算の途中から例外が出る)
30 try:
31 return compute()
32 except Exception as e:
33 logger.exception("ピーク検出に失敗しました")
34 op.stop_with_warning(f"エラーが発生しました:\n{e}", title="ピーク検出エラー")
37def find_peaks(op):
38 """現在のデータセットのピーク(または谷)を探し、位置のデータセットと定量結果の表を出す。"""
39 source = op.current_dataset()
40 settings = _ask_settings(op, source)
41 peak_type = settings.get("peak_type", _DEFAULT_PEAK_TYPE)
43 quant = _detect(op, lambda: calculate_peak_quantification(source.x_data, source.y_data, peak_type, settings))
45 peak_x, peak_y = quant['peak_x'], quant['peak_y']
46 fwhm, area, centroid = quant['fwhm'], quant['area'], quant['centroid']
47 if len(peak_x) == 0:
48 op.stop_with_information(f"指定された条件で {peak_type} は見つかりませんでした。")
50 is_valley = "下に凸" in peak_type
51 op.host.add_derived_dataset(Dataset(
52 name=f"{peak_type.split(' ')[0]} ({source.name})",
53 df=pd.DataFrame({'peak_x': peak_x, 'peak_y': peak_y}),
54 x_col_name='peak_x', y_col_name='peak_y',
55 plot_type='Scatter', color='blue' if is_valley else 'red', linestyle='None',
56 marker='^' if is_valley else 'v', markersize=8,
57 use_secondary_y=source.use_secondary_y,
58 subplot_target=source.subplot_target,
59 ), source, op.title)
61 order = np.argsort(peak_x)
62 text = (
63 f"検出された {peak_type} ({len(peak_x)}個):\n"
64 " X座標\t\tY座標\t\tFWHM\t\t面積\t\t重心X\n" + "-" * 70 + "\n"
65 )
66 for i in order:
67 text += (f" {peak_x[i]:.4g}\t\t{peak_y[i]:.4g}\t\t{fwhm[i]:.4g}"
68 f"\t\t{area[i]:.4g}\t\t{centroid[i]:.4g}\n")
69 table = pd.DataFrame({
70 'X座標': peak_x[order], 'Y座標': peak_y[order], 'FWHM': fwhm[order],
71 '面積': area[order], '重心X': centroid[order],
72 })
73 op.show_result_window(PEAK_RESULT_WINDOW, lambda: ResultDialog("ピーク検出完了", text, op.parent, csv_data=table))
76def add_peak_labels(op):
77 """
78 検出した各ピークの位置に X 値のラベルを付ける。近いピークは段を分けて縦にずらし、重ならないようにする。
79 ずらす幅は軸の Y 表示幅に対する比率なので、スケールの違うデータでも見た目の間隔がそろう。
80 """
81 dataset = op.current_dataset()
82 settings = _ask_settings(op, dataset)
83 peak_type = settings.get("peak_type", _DEFAULT_PEAK_TYPE)
85 peak_x, peak_y = _detect(op, lambda: calculate_peaks(dataset.x_data, dataset.y_data, peak_type, settings))
86 if len(peak_x) == 0:
87 op.stop_with_information(f"指定された条件で {peak_type} は見つかりませんでした。")
89 order = np.argsort(peak_x)
90 peak_x, peak_y = peak_x[order], peak_y[order]
91 axis_index = dataset.subplot_target
92 y_span = op.host.axis_y_span(axis_index)
93 if y_span is None: 93 ↛ 94line 93 didn't jump to line 94 because the condition on line 93 was never true
94 y_span = 1.0
95 x_span = float(np.max(peak_x) - np.min(peak_x)) if len(peak_x) > 1 else 0.0
96 levels = assign_peak_label_levels(peak_x, x_span)
98 with op.host.undo_macro(f"{op.title}追加 ({len(peak_x)}件)", enabled=len(peak_x) > 1):
99 for x, y, level in zip(peak_x, peak_y, levels):
100 op.host.add_annotation(axis_index, {
101 'type': 'text', 'text': f"{x:.4g}",
102 'xy': (float(x), float(y + y_span * (0.06 + 0.05 * level))), 'color': '#000000',
103 }, description=f"{op.title}追加")
106PEAK_OPERATIONS = {
107 "find_peaks": Operation("ピーク検出", find_peaks),
108 "add_peak_labels": Operation("ピーク位置への自動ラベル", add_peak_labels),
109}