步骤四的采样点和对应的光谱图像展示修正
This commit is contained in:
@ -7,7 +7,8 @@ Step4 面板 - 采样点布设(内嵌交互式光谱探针视图)
|
||||
- 左右分栏布局 (QSplitter):左侧控制区 + 右侧嵌入式 Matplotlib 视图
|
||||
- 1×2 子图:ax1 散点图 + ax2 光谱曲线
|
||||
- Hover 悬停显示坐标提示,Click 点击绘制该点光谱曲线
|
||||
- NavigationToolbar2QT 工具栏自带保存/缩放/平移
|
||||
- 完美复刻 Step12 风格的自定义独立顶部工具栏,隐藏原生丑陋图标
|
||||
- 修复 Matplotlib 3.3+ 版本兼容性导致的 _active 属性报错
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -48,6 +49,10 @@ from src.gui.styles import ModernStylesheet
|
||||
class Step4SamplingPanel(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
# ── 全局中文字体强制设置(必须在创建 Figure 之前,与模块级 rcParams 形成双重保障)──
|
||||
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'sans-serif']
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
# 交互状态
|
||||
self._df = None
|
||||
self._x_col = None
|
||||
@ -59,6 +64,7 @@ class Step4SamplingPanel(QWidget):
|
||||
self._cid_hover = None
|
||||
self._cid_click = None
|
||||
self._last_render_path = None
|
||||
|
||||
self.init_ui()
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@ -174,20 +180,65 @@ class Step4SamplingPanel(QWidget):
|
||||
left_widget.setMinimumWidth(360)
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# 右侧:可视化区(参考 Step12 ImageViewer 的白底卡片风格)
|
||||
# 右侧:可视化区(深度复刻 Step12 样式)
|
||||
# ═══════════════════════════════════════════════
|
||||
right_widget = QWidget()
|
||||
right_layout = QVBoxLayout()
|
||||
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||
right_layout.setSpacing(0)
|
||||
|
||||
# 给画板穿上卡片外衣,确保视觉上的绝对统一
|
||||
viz_group = QGroupBox("📊 采样点交互式探索")
|
||||
viz_layout = QVBoxLayout()
|
||||
viz_layout.setContentsMargins(8, 20, 8, 8)
|
||||
viz_layout.setSpacing(0)
|
||||
viz_layout.setSpacing(8)
|
||||
|
||||
# Matplotlib 画布(1×2 子图:散点图 + 光谱曲线)
|
||||
# ── 学习 Step 12:重构标准的 PyQt 工具栏 ──
|
||||
custom_toolbar = QHBoxLayout()
|
||||
|
||||
self.fit_btn = QPushButton("⬜ 适应窗口")
|
||||
self.fit_btn.setToolTip("恢复默认全景视图")
|
||||
custom_toolbar.addWidget(self.fit_btn)
|
||||
|
||||
separator = QFrame()
|
||||
separator.setFrameShape(QFrame.VLine)
|
||||
separator.setFrameShadow(QFrame.Sunken)
|
||||
custom_toolbar.addWidget(separator)
|
||||
|
||||
# 专门为交互工具按钮定制的悬停与选中(蓝色)高亮样式
|
||||
tool_btn_style = """
|
||||
QPushButton { padding: 5px 10px; border-radius: 4px; border: 1px solid transparent; background: transparent; color: #475569; font-weight: bold; }
|
||||
QPushButton:hover { background-color: #F1F5F9; border: 1px solid #CBD5E1; color: #0F172A; }
|
||||
QPushButton:checked { background-color: #E0F2FE; color: #0369A1; border: 1px solid #BAE6FD; }
|
||||
"""
|
||||
|
||||
self.probe_btn = QPushButton("👆 点选探针")
|
||||
self.probe_btn.setToolTip("点击散点查看光谱曲线(默认模式)")
|
||||
self.probe_btn.setCheckable(True)
|
||||
self.probe_btn.setChecked(True) # 默认激活
|
||||
self.probe_btn.setStyleSheet(tool_btn_style)
|
||||
custom_toolbar.addWidget(self.probe_btn)
|
||||
|
||||
self.pan_btn = QPushButton("✋ 拖拽漫游")
|
||||
self.pan_btn.setToolTip("按住左键拖拽平移图表")
|
||||
self.pan_btn.setCheckable(True)
|
||||
self.pan_btn.setStyleSheet(tool_btn_style)
|
||||
custom_toolbar.addWidget(self.pan_btn)
|
||||
|
||||
self.zoom_rect_btn = QPushButton("🔍 框选放大")
|
||||
self.zoom_rect_btn.setToolTip("框选局部区域进行放大")
|
||||
self.zoom_rect_btn.setCheckable(True)
|
||||
self.zoom_rect_btn.setStyleSheet(tool_btn_style)
|
||||
custom_toolbar.addWidget(self.zoom_rect_btn)
|
||||
|
||||
custom_toolbar.addStretch()
|
||||
|
||||
self.save_btn = QPushButton("💾 保存图像")
|
||||
self.save_btn.setToolTip("保存当前图表到本地")
|
||||
custom_toolbar.addWidget(self.save_btn)
|
||||
|
||||
viz_layout.addLayout(custom_toolbar)
|
||||
|
||||
# ── 画布渲染区 ──
|
||||
self._fig = Figure(figsize=(10, 7), facecolor='white')
|
||||
self._ax_scatter = self._fig.add_subplot(121)
|
||||
self._ax_spectrum = self._fig.add_subplot(122)
|
||||
@ -195,6 +246,16 @@ class Step4SamplingPanel(QWidget):
|
||||
self._canvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self._canvas.setStyleSheet("background-color: white;")
|
||||
|
||||
# ── 彻底隐藏原生引擎并绑定到前台按钮 ──
|
||||
self._hidden_toolbar = NavigationToolbar(self._canvas, self)
|
||||
self._hidden_toolbar.hide()
|
||||
|
||||
self.fit_btn.clicked.connect(self._hidden_toolbar.home)
|
||||
self.probe_btn.clicked.connect(self._toggle_probe)
|
||||
self.pan_btn.clicked.connect(self._toggle_pan)
|
||||
self.zoom_rect_btn.clicked.connect(self._toggle_zoom)
|
||||
self.save_btn.clicked.connect(self._hidden_toolbar.save_figure)
|
||||
|
||||
# 设置子图初始状态
|
||||
for ax, title, xlabel, ylabel in [
|
||||
(self._ax_scatter, "采样点空间分布", "经度 / X", "纬度 / Y"),
|
||||
@ -214,10 +275,6 @@ class Step4SamplingPanel(QWidget):
|
||||
fontsize=13, color='#AAAAAA')
|
||||
|
||||
self._fig.tight_layout(pad=2.0)
|
||||
|
||||
# 工具栏(自带保存/缩放/平移)
|
||||
self._toolbar = NavigationToolbar(self._canvas, self)
|
||||
viz_layout.addWidget(self._toolbar)
|
||||
viz_layout.addWidget(self._canvas)
|
||||
|
||||
viz_group.setLayout(viz_layout)
|
||||
@ -248,7 +305,50 @@ class Step4SamplingPanel(QWidget):
|
||||
self.output_file.line_edit.textChanged.connect(self._on_output_changed)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 配置读写(保持不变)
|
||||
# 原生交互工具代理 (兼容新旧版 Matplotlib)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _toggle_probe(self):
|
||||
"""探针模式:解除原生引擎工具,恢复点击选点交互"""
|
||||
# 取消 Matplotlib 原生工具的激活状态(回到无工具模式)
|
||||
if hasattr(self._hidden_toolbar, 'mode'):
|
||||
active = str(getattr(self._hidden_toolbar.mode, 'name', self._hidden_toolbar.mode)).upper()
|
||||
else:
|
||||
active = str(getattr(self._hidden_toolbar, '_active', '')).upper()
|
||||
# 如果当前有激活的工具,再点一次让它取消
|
||||
if 'PAN' in active:
|
||||
self._hidden_toolbar.pan()
|
||||
elif 'ZOOM' in active:
|
||||
self._hidden_toolbar.zoom()
|
||||
self._sync_button_states()
|
||||
|
||||
def _toggle_pan(self):
|
||||
"""联动原生引擎的拖拽功能,并控制按钮激活状态"""
|
||||
self._hidden_toolbar.pan()
|
||||
self._sync_button_states()
|
||||
|
||||
def _toggle_zoom(self):
|
||||
"""联动原生引擎的框选放大功能,并控制按钮激活状态"""
|
||||
self._hidden_toolbar.zoom()
|
||||
self._sync_button_states()
|
||||
|
||||
def _sync_button_states(self):
|
||||
"""兼容新老版 Matplotlib 获取当前激活模式,实现三按钮互斥"""
|
||||
if hasattr(self._hidden_toolbar, 'mode'):
|
||||
mode_str = str(getattr(self._hidden_toolbar.mode, 'name', self._hidden_toolbar.mode)).upper()
|
||||
else:
|
||||
mode_str = str(getattr(self._hidden_toolbar, '_active', '')).upper()
|
||||
|
||||
is_pan = 'PAN' in mode_str
|
||||
is_zoom = 'ZOOM' in mode_str
|
||||
is_probe = not is_pan and not is_zoom # 探针 = 两者都不激活
|
||||
|
||||
self.probe_btn.setChecked(is_probe)
|
||||
self.pan_btn.setChecked(is_pan)
|
||||
self.zoom_rect_btn.setChecked(is_zoom)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 配置读写
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def get_config(self):
|
||||
@ -288,17 +388,14 @@ class Step4SamplingPanel(QWidget):
|
||||
else:
|
||||
self.work_dir = None
|
||||
|
||||
# ── 去耀斑影像输入 ──
|
||||
# 优先:pipeline context
|
||||
deglint_path = None
|
||||
if pipeline and hasattr(pipeline, 'step_outputs'):
|
||||
step3_out = pipeline.step_outputs.get('step3', {})
|
||||
deglint_path = (
|
||||
step3_out.get('deglint_image') or step3_out.get('output_path') or
|
||||
step3_out.get('output_file') or step3_out.get('deglint_img_path')
|
||||
step3_out.get('deglint_image') or step3_out.get('output_path') or
|
||||
step3_out.get('output_file') or step3_out.get('deglint_img_path')
|
||||
)
|
||||
|
||||
# 回退:文件系统扫描 3_deglint/
|
||||
if not deglint_path or not os.path.exists(deglint_path):
|
||||
deglint_path = scan_work_dir_for_input(self.work_dir, 'deglint_image')
|
||||
|
||||
@ -307,17 +404,14 @@ class Step4SamplingPanel(QWidget):
|
||||
deglint_path = os.path.join(self.work_dir or '', deglint_path).replace('\\', '/')
|
||||
self.deglint_img_file.set_path(deglint_path)
|
||||
|
||||
# ── 水域掩膜输入 ──
|
||||
# 优先:pipeline context
|
||||
water_mask_path = None
|
||||
if pipeline and hasattr(pipeline, 'step_outputs'):
|
||||
step1_out = pipeline.step_outputs.get('step1', {})
|
||||
water_mask_path = (
|
||||
step1_out.get('water_mask') or step1_out.get('output_path') or
|
||||
step1_out.get('output_file')
|
||||
step1_out.get('water_mask') or step1_out.get('output_path') or
|
||||
step1_out.get('output_file')
|
||||
)
|
||||
|
||||
# 回退:文件系统扫描 1_water_mask/
|
||||
if not water_mask_path or not os.path.exists(water_mask_path):
|
||||
water_mask_path = scan_work_dir_for_input(self.work_dir, 'water_mask')
|
||||
|
||||
@ -326,13 +420,11 @@ class Step4SamplingPanel(QWidget):
|
||||
water_mask_path = os.path.join(self.work_dir or '', water_mask_path).replace('\\', '/')
|
||||
self.water_mask_file.set_path(water_mask_path)
|
||||
|
||||
# ── 输出路径 ──
|
||||
if self.work_dir and not self.output_file.get_path():
|
||||
output_path = resolve_subdir(self.work_dir, 'sampling_csv_path')
|
||||
self.output_file.set_path(output_path.replace('\\', '/'))
|
||||
|
||||
self._check_csv_exists()
|
||||
# 若 CSV 已存在,尝试自动渲染
|
||||
csv_path = self.output_file.get_path()
|
||||
if csv_path and os.path.isfile(csv_path):
|
||||
self._render_inline_plot()
|
||||
@ -367,7 +459,6 @@ class Step4SamplingPanel(QWidget):
|
||||
self._check_csv_exists()
|
||||
|
||||
def _check_csv_and_auto_render(self):
|
||||
"""定时器回调:检测到新 CSV 出现时自动渲染一次。"""
|
||||
csv_path = self.output_file.get_path()
|
||||
if csv_path and os.path.isfile(csv_path):
|
||||
if csv_path != self._last_render_path:
|
||||
@ -375,7 +466,6 @@ class Step4SamplingPanel(QWidget):
|
||||
self._render_inline_plot()
|
||||
|
||||
def _on_refresh_clicked(self):
|
||||
"""手动点击刷新按钮。"""
|
||||
csv_path = self.output_file.get_path()
|
||||
if not csv_path or not os.path.exists(csv_path):
|
||||
QMessageBox.warning(self, "文件不存在", f"采样点 CSV 文件不存在:{csv_path}\n请先运行生成数据。")
|
||||
@ -387,13 +477,7 @@ class Step4SamplingPanel(QWidget):
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _detect_coordinate_columns(self, df: pd.DataFrame):
|
||||
"""检测坐标列,返回 (x_col, y_col) 或 (None, None)。
|
||||
|
||||
优先级:pixel_x/pixel_y → longitude/latitude → lon/lat →
|
||||
X/Y → UTM_X/UTM_Y → 任何含 'x'/'y' 关键字的列
|
||||
"""
|
||||
cols_lower = {c.lower(): c for c in df.columns}
|
||||
|
||||
priority_pairs = [
|
||||
('pixel_x', 'pixel_y'),
|
||||
('longitude', 'latitude'),
|
||||
@ -406,7 +490,6 @@ class Step4SamplingPanel(QWidget):
|
||||
if x_key in cols_lower and y_key in cols_lower:
|
||||
return cols_lower[x_key], cols_lower[y_key]
|
||||
|
||||
# 最后尝试:找名字中含 x / y 的数值列
|
||||
x_candidates = [c for c in df.columns if 'x' in c.lower() and pd.api.types.is_numeric_dtype(df[c])]
|
||||
y_candidates = [c for c in df.columns if 'y' in c.lower() and pd.api.types.is_numeric_dtype(df[c])]
|
||||
if x_candidates and y_candidates:
|
||||
@ -415,11 +498,6 @@ class Step4SamplingPanel(QWidget):
|
||||
return None, None
|
||||
|
||||
def _detect_band_columns(self, df: pd.DataFrame):
|
||||
"""检测光谱波段列(纯数字列名,值域在 200–3000 nm 之间)。
|
||||
|
||||
优先使用列名可解析为 float 且在波长范围内的列;
|
||||
否则回退到位置索引(跳过坐标列和已知元数据列)。
|
||||
"""
|
||||
band_cols = []
|
||||
for col in df.columns:
|
||||
try:
|
||||
@ -430,11 +508,9 @@ class Step4SamplingPanel(QWidget):
|
||||
continue
|
||||
|
||||
if band_cols:
|
||||
# 按波长数值排序
|
||||
band_cols.sort(key=lambda c: float(str(c).strip()))
|
||||
return band_cols
|
||||
|
||||
# 回退:跳过坐标列和已知元数据列,取数值列
|
||||
skip_keywords = ('x', 'y', 'lon', 'lat', 'utm', 'id', 'sample', 'index', 'pixel')
|
||||
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
||||
if self._x_col:
|
||||
@ -444,13 +520,11 @@ class Step4SamplingPanel(QWidget):
|
||||
return [c for c in numeric_cols if not any(k in c.lower() for k in skip_keywords)]
|
||||
|
||||
def _render_inline_plot(self):
|
||||
"""读取 CSV → 检测坐标/波段列 → 绘制 1×2 子图。"""
|
||||
csv_path = self.output_file.get_path()
|
||||
if not csv_path or not os.path.isfile(csv_path):
|
||||
self._show_empty_state("等待采样数据生成...\n\n请先配置参数并运行步骤")
|
||||
return
|
||||
|
||||
# 读取数据
|
||||
try:
|
||||
df = pd.read_csv(csv_path)
|
||||
except Exception as e:
|
||||
@ -461,7 +535,6 @@ class Step4SamplingPanel(QWidget):
|
||||
self._show_empty_state("CSV 文件为空")
|
||||
return
|
||||
|
||||
# 检测坐标列
|
||||
x_col, y_col = self._detect_coordinate_columns(df)
|
||||
if x_col is None or y_col is None:
|
||||
self._show_empty_state(
|
||||
@ -471,13 +544,8 @@ class Step4SamplingPanel(QWidget):
|
||||
)
|
||||
return
|
||||
|
||||
# 检测光谱列
|
||||
band_cols = self._detect_band_columns(df)
|
||||
if not band_cols:
|
||||
# 仅显示散点图,光谱子图留空
|
||||
pass
|
||||
|
||||
# 缓存
|
||||
self._df = df
|
||||
self._x_col = x_col
|
||||
self._y_col = y_col
|
||||
@ -485,7 +553,6 @@ class Step4SamplingPanel(QWidget):
|
||||
self._highlight_idx = None
|
||||
self._last_render_path = csv_path
|
||||
|
||||
# 清除 Annotation
|
||||
if self._annot is not None:
|
||||
try:
|
||||
self._annot.remove()
|
||||
@ -493,7 +560,6 @@ class Step4SamplingPanel(QWidget):
|
||||
pass
|
||||
self._annot = None
|
||||
|
||||
# ── 绘制 ax1:散点图 ──
|
||||
self._ax_scatter.clear()
|
||||
self._ax_scatter.set_facecolor('white')
|
||||
x = df[x_col].values
|
||||
@ -510,7 +576,6 @@ class Step4SamplingPanel(QWidget):
|
||||
self._ax_scatter.grid(True, alpha=0.2, linestyle='-', linewidth=0.5)
|
||||
self._ax_scatter.tick_params(labelsize=9)
|
||||
|
||||
# ── 绘制 ax2:光谱曲线(初始状态)──
|
||||
self._ax_spectrum.clear()
|
||||
self._ax_spectrum.set_facecolor('white')
|
||||
if band_cols:
|
||||
@ -528,10 +593,9 @@ class Step4SamplingPanel(QWidget):
|
||||
fontsize=13, color='#AAAAAA')
|
||||
|
||||
self._fig.tight_layout(pad=2.0)
|
||||
self._canvas.draw()
|
||||
self._canvas.draw_idle()
|
||||
|
||||
def _show_empty_state(self, message: str):
|
||||
"""在右侧画布显示提示信息。"""
|
||||
for ax in (self._ax_scatter, self._ax_spectrum):
|
||||
ax.clear()
|
||||
ax.set_facecolor('white')
|
||||
@ -548,7 +612,7 @@ class Step4SamplingPanel(QWidget):
|
||||
fontsize=13, color='#AAAAAA')
|
||||
|
||||
self._fig.tight_layout(pad=2.0)
|
||||
self._canvas.draw()
|
||||
self._canvas.draw_idle()
|
||||
self._df = None
|
||||
self._last_render_path = None
|
||||
|
||||
@ -557,9 +621,8 @@ class Step4SamplingPanel(QWidget):
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _on_hover(self, event):
|
||||
"""悬停:在散点旁显示坐标 + ID 提示框。"""
|
||||
if event.inaxes != self._ax_scatter:
|
||||
# 鼠标离开 ax1 → 隐藏 annotation
|
||||
self._canvas.unsetCursor() # 离开绘图区,恢复默认鼠标
|
||||
if self._annot is not None:
|
||||
try:
|
||||
self._annot.set_visible(False)
|
||||
@ -571,9 +634,22 @@ class Step4SamplingPanel(QWidget):
|
||||
if self._df is None or self._scatter is None:
|
||||
return
|
||||
|
||||
# 检测是否悬停在散点上
|
||||
# ── 拦截:如果当前拿着拖拽或缩放工具,交出鼠标控制权给 Matplotlib ──
|
||||
if hasattr(self._hidden_toolbar, 'mode'):
|
||||
mode_str = str(getattr(self._hidden_toolbar.mode, 'name', self._hidden_toolbar.mode)).upper()
|
||||
else:
|
||||
mode_str = str(getattr(self._hidden_toolbar, '_active', '')).upper()
|
||||
|
||||
if 'PAN' in mode_str or 'ZOOM' in mode_str:
|
||||
self._canvas.unsetCursor() # 解除我们的控制,让工具管鼠标
|
||||
if self._annot is not None and self._annot.get_visible():
|
||||
self._annot.set_visible(False)
|
||||
self._canvas.draw_idle()
|
||||
return
|
||||
|
||||
contains, info = self._scatter.contains(event)
|
||||
if not contains or info is None or 'ind' not in info or len(info['ind']) == 0:
|
||||
self._canvas.setCursor(Qt.ArrowCursor) # 在空白处,变成普通箭头
|
||||
if self._annot is not None:
|
||||
try:
|
||||
self._annot.set_visible(False)
|
||||
@ -582,12 +658,14 @@ class Step4SamplingPanel(QWidget):
|
||||
pass
|
||||
return
|
||||
|
||||
# ── 【鼠标变小手】:悬停在散点上,变成可点击的"小手" ──
|
||||
self._canvas.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
idx = info['ind'][0]
|
||||
row = self._df.iloc[idx]
|
||||
x_val = row[self._x_col]
|
||||
y_val = row[self._y_col]
|
||||
|
||||
# 创建或更新 Annotation
|
||||
text = f"ID: {idx}\n经度: {x_val:.4f}\n纬度: {y_val:.4f}"
|
||||
if self._annot is None:
|
||||
self._annot = self._ax_scatter.annotate(
|
||||
@ -608,23 +686,68 @@ class Step4SamplingPanel(QWidget):
|
||||
self._canvas.draw_idle()
|
||||
|
||||
def _on_click(self, event):
|
||||
"""点击:高亮散点 + 绘制光谱曲线。"""
|
||||
if event.inaxes != self._ax_scatter:
|
||||
return
|
||||
|
||||
if self._df is None or self._scatter is None:
|
||||
return
|
||||
|
||||
# 检测是否点击在散点上
|
||||
# ── 拦截:如果当前拿着拖拽或缩放工具,直接忽略选点 ──
|
||||
if hasattr(self._hidden_toolbar, 'mode'):
|
||||
mode_str = str(getattr(self._hidden_toolbar.mode, 'name', self._hidden_toolbar.mode)).upper()
|
||||
else:
|
||||
mode_str = str(getattr(self._hidden_toolbar, '_active', '')).upper()
|
||||
|
||||
if 'PAN' in mode_str or 'ZOOM' in mode_str:
|
||||
return
|
||||
|
||||
contains, info = self._scatter.contains(event)
|
||||
if not contains or info is None or 'ind' not in info or len(info['ind']) == 0:
|
||||
return
|
||||
|
||||
idx = info['ind'][0]
|
||||
row = self._df.iloc[idx]
|
||||
self._highlight_idx = idx
|
||||
|
||||
# ── 高亮选中点 ──
|
||||
# ── 【取消选中逻辑】:如果点击的是已经高亮的点,则取消高亮并重置 ──
|
||||
if getattr(self, '_highlight_idx', None) == idx:
|
||||
self._highlight_idx = None
|
||||
current_xlim = self._ax_scatter.get_xlim()
|
||||
current_ylim = self._ax_scatter.get_ylim()
|
||||
|
||||
# 恢复左侧为全是蓝色散点的状态
|
||||
self._ax_scatter.clear()
|
||||
self._ax_scatter.set_facecolor('white')
|
||||
x = self._df[self._x_col].values
|
||||
y = self._df[self._y_col].values
|
||||
self._scatter = self._ax_scatter.scatter(
|
||||
x, y, c='#0078D7', alpha=0.75, edgecolors='white', linewidth=0.6, s=45, picker=True, zorder=3
|
||||
)
|
||||
self._ax_scatter.set_xlabel("经度 / X", fontsize=11)
|
||||
self._ax_scatter.set_ylabel("纬度 / Y", fontsize=11)
|
||||
self._ax_scatter.set_title(f"采样点空间分布 (共 {len(self._df)} 个点)", fontsize=14, fontweight='bold', pad=12)
|
||||
self._ax_scatter.grid(True, alpha=0.2, linestyle='-', linewidth=0.5)
|
||||
self._ax_scatter.tick_params(labelsize=9)
|
||||
self._ax_scatter.set_xlim(current_xlim)
|
||||
self._ax_scatter.set_ylim(current_ylim)
|
||||
|
||||
# 清空右侧光谱图
|
||||
self._ax_spectrum.clear()
|
||||
self._ax_spectrum.set_facecolor('white')
|
||||
self._ax_spectrum.set_title("光谱曲线(点击左侧散点查看)", fontsize=14, fontweight='bold', pad=12)
|
||||
self._ax_spectrum.set_xlabel("波长 / 特征", fontsize=11)
|
||||
self._ax_spectrum.set_ylabel("反射率", fontsize=11)
|
||||
self._ax_spectrum.grid(True, alpha=0.2, linestyle='-', linewidth=0.5)
|
||||
self._ax_spectrum.tick_params(labelsize=9)
|
||||
self._ax_spectrum.text(0.5, 0.5, "点击左侧散点\n查看光谱曲线",
|
||||
ha='center', va='center', transform=self._ax_spectrum.transAxes,
|
||||
fontsize=13, color='#AAAAAA')
|
||||
self._fig.tight_layout(pad=2.0)
|
||||
self._canvas.draw_idle()
|
||||
return
|
||||
|
||||
# ── 正常选中并高亮逻辑 ──
|
||||
self._highlight_idx = idx
|
||||
row = self._df.iloc[idx]
|
||||
|
||||
x = self._df[self._x_col].values
|
||||
y = self._df[self._y_col].values
|
||||
|
||||
@ -633,6 +756,9 @@ class Step4SamplingPanel(QWidget):
|
||||
colors[idx] = '#E74C3C'
|
||||
sizes[idx] = 80
|
||||
|
||||
current_xlim = self._ax_scatter.get_xlim()
|
||||
current_ylim = self._ax_scatter.get_ylim()
|
||||
|
||||
self._ax_scatter.clear()
|
||||
self._ax_scatter.set_facecolor('white')
|
||||
self._scatter = self._ax_scatter.scatter(
|
||||
@ -640,7 +766,6 @@ class Step4SamplingPanel(QWidget):
|
||||
alpha=0.75, edgecolors='white', linewidth=0.6,
|
||||
picker=True, zorder=3
|
||||
)
|
||||
# 将选中点提升到顶层
|
||||
self._ax_scatter.scatter(
|
||||
[x[idx]], [y[idx]],
|
||||
c='#E74C3C', s=110,
|
||||
@ -652,8 +777,9 @@ class Step4SamplingPanel(QWidget):
|
||||
self._ax_scatter.set_title(f"采样点空间分布 (共 {len(self._df)} 个点)", fontsize=14, fontweight='bold', pad=12)
|
||||
self._ax_scatter.grid(True, alpha=0.2, linestyle='-', linewidth=0.5)
|
||||
self._ax_scatter.tick_params(labelsize=9)
|
||||
self._ax_scatter.set_xlim(current_xlim)
|
||||
self._ax_scatter.set_ylim(current_ylim)
|
||||
|
||||
# ── 绘制光谱曲线 ──
|
||||
self._ax_spectrum.clear()
|
||||
self._ax_spectrum.set_facecolor('white')
|
||||
if self._band_cols:
|
||||
@ -670,7 +796,6 @@ class Step4SamplingPanel(QWidget):
|
||||
continue
|
||||
|
||||
if wavelengths:
|
||||
# 按波长排序
|
||||
pairs = sorted(zip(wavelengths, reflectance), key=lambda p: p[0])
|
||||
wavelengths, reflectance = zip(*pairs) if pairs else ([], [])
|
||||
self._ax_spectrum.plot(
|
||||
@ -696,14 +821,13 @@ class Step4SamplingPanel(QWidget):
|
||||
fontsize=13, color='#AAAAAA')
|
||||
|
||||
self._fig.tight_layout(pad=2.0)
|
||||
self._canvas.draw()
|
||||
self._canvas.draw_idle()
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 旧版弹窗查看器(保留,供外部调用)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _open_sampling_viewer(self):
|
||||
"""打开独立的 SamplingViewerDialog 弹窗(保留兼容)。"""
|
||||
csv_path = self.output_file.get_path()
|
||||
if not csv_path or not os.path.exists(csv_path):
|
||||
QMessageBox.warning(self, "文件不存在", f"采样点 CSV 文件不存在:{csv_path}\n请先运行生成数据。")
|
||||
|
||||
Reference in New Issue
Block a user