feat(visualization+report): 接入 Step9 浓度反演数据至可视化面板与报告生成器
This commit is contained in:
@ -359,6 +359,64 @@ class VisualizationWorkerThread(QThread):
|
|||||||
parts.append("采样点图: 跳过(无CSV)")
|
parts.append("采样点图: 跳过(无CSV)")
|
||||||
else:
|
else:
|
||||||
parts.append("采样点图: 跳过(无影像)")
|
parts.append("采样点图: 跳过(无影像)")
|
||||||
|
|
||||||
|
if self.extra.get("gen_concentration"):
|
||||||
|
conc_dir = wp / "9_Concentration"
|
||||||
|
conc_csv = conc_dir / "final_concentrations.csv"
|
||||||
|
if conc_csv.is_file():
|
||||||
|
charts_dir = conc_dir / "charts"
|
||||||
|
charts_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
import pandas as pd
|
||||||
|
df = pd.read_csv(conc_csv)
|
||||||
|
exclude_kw = (
|
||||||
|
"wavelength", "lon", "lat", "utm_x", "utm_y",
|
||||||
|
"x", "y", "coord", "longitude", "latitude",
|
||||||
|
"sample_id", "id", "index", "name", "pixel",
|
||||||
|
)
|
||||||
|
conc_cols = [
|
||||||
|
c for c in df.select_dtypes(include=[np.number]).columns
|
||||||
|
if not any(k in str(c).lower() for k in exclude_kw)
|
||||||
|
]
|
||||||
|
if conc_cols:
|
||||||
|
orig_out = viz.output_dir
|
||||||
|
viz.output_dir = str(charts_dir)
|
||||||
|
output_dict = viz.plot_statistical_charts(
|
||||||
|
csv_path=str(conc_csv),
|
||||||
|
parameter_columns=conc_cols,
|
||||||
|
)
|
||||||
|
viz.output_dir = orig_out
|
||||||
|
count = len([v for v in output_dict.values() if v])
|
||||||
|
parts.append(f"浓度统计图: {count} 个")
|
||||||
|
stats_rows = []
|
||||||
|
for col in conc_cols:
|
||||||
|
s = df[col].dropna()
|
||||||
|
if len(s) == 0:
|
||||||
|
continue
|
||||||
|
stats_rows.append({
|
||||||
|
"参数": col,
|
||||||
|
"点位数": len(s),
|
||||||
|
"最小值": round(float(s.min()), 4),
|
||||||
|
"最大值": round(float(s.max()), 4),
|
||||||
|
"均值": round(float(s.mean()), 4),
|
||||||
|
"中位数": round(float(s.median()), 4),
|
||||||
|
"标准差": round(float(s.std()), 4) if len(s) > 1 else 0.0,
|
||||||
|
})
|
||||||
|
if stats_rows:
|
||||||
|
pd.DataFrame(stats_rows).to_csv(
|
||||||
|
conc_dir / "statistics_summary.csv",
|
||||||
|
index=False,
|
||||||
|
encoding="utf-8-sig",
|
||||||
|
)
|
||||||
|
parts.append("浓度统计表: 已生成")
|
||||||
|
else:
|
||||||
|
parts.append("浓度统计表: 跳过(无可用列)")
|
||||||
|
else:
|
||||||
|
parts.append("浓度统计图: 跳过(无可用浓度列)")
|
||||||
|
except Exception as e:
|
||||||
|
parts.append(f"浓度统计图: 失败({e})")
|
||||||
|
else:
|
||||||
|
parts.append("浓度统计图: 跳过(无浓度CSV)")
|
||||||
self.finished_ok.emit({"task": "generate_all_selected", "parts": parts})
|
self.finished_ok.emit({"task": "generate_all_selected", "parts": parts})
|
||||||
else:
|
else:
|
||||||
self.failed.emit(f"未知可视化任务: {self.task}")
|
self.failed.emit(f"未知可视化任务: {self.task}")
|
||||||
@ -568,6 +626,7 @@ class ImageCategoryTree(QTreeWidget):
|
|||||||
"output_dir": "输出目录",
|
"output_dir": "输出目录",
|
||||||
"8_spatial_inversion": "空间反演",
|
"8_spatial_inversion": "空间反演",
|
||||||
"4_processed_data": "处理数据",
|
"4_processed_data": "处理数据",
|
||||||
|
"9_Concentration": "物理反演浓度分布",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@ -767,6 +826,7 @@ class ImageCategoryTree(QTreeWidget):
|
|||||||
self._work_path / "3_deglint",
|
self._work_path / "3_deglint",
|
||||||
self._work_path / "1_water_mask",
|
self._work_path / "1_water_mask",
|
||||||
self._work_path / "9_water_quality_prediction",
|
self._work_path / "9_water_quality_prediction",
|
||||||
|
self._work_path / "9_Concentration",
|
||||||
]
|
]
|
||||||
|
|
||||||
# 只保留存在的目录,并补充工作根目录作为兜底
|
# 只保留存在的目录,并补充工作根目录作为兜底
|
||||||
|
|||||||
@ -834,7 +834,12 @@ class WaterQualityReportGenerator:
|
|||||||
figure_counter = self._add_result_analysis_section(
|
figure_counter = self._add_result_analysis_section(
|
||||||
doc, vis_dir, figure_counter, all_image_analyses, progress=progress
|
doc, vis_dir, figure_counter, all_image_analyses, progress=progress
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 物理模型反演浓度统计与分析(第4.1节)
|
||||||
|
figure_counter = self._add_physical_inversion_section(
|
||||||
|
doc, self.work_dir, figure_counter, all_image_analyses, progress=progress
|
||||||
|
)
|
||||||
|
|
||||||
# 设置页眉和页码(从正文开始)
|
# 设置页眉和页码(从正文开始)
|
||||||
self._setup_header_and_footer(section)
|
self._setup_header_and_footer(section)
|
||||||
|
|
||||||
@ -1747,6 +1752,90 @@ class WaterQualityReportGenerator:
|
|||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
return start_figure_num + (1 if heatmap_path.exists() else 0)
|
return start_figure_num + (1 if heatmap_path.exists() else 0)
|
||||||
|
|
||||||
|
def _add_physical_inversion_section(
|
||||||
|
self,
|
||||||
|
doc: Document,
|
||||||
|
work_dir: Path,
|
||||||
|
start_figure_num: int = 1,
|
||||||
|
all_image_analyses: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
progress=None,
|
||||||
|
) -> int:
|
||||||
|
"""新增章节:物理模型反演浓度统计与分析(第4节之后)"""
|
||||||
|
conc_dir = work_dir / "9_Concentration"
|
||||||
|
if not conc_dir.is_dir():
|
||||||
|
doc.add_paragraph("[物理反演浓度章节:9_Concentration 目录不存在,已跳过]")
|
||||||
|
return start_figure_num
|
||||||
|
|
||||||
|
stats_csv = conc_dir / "statistics_summary.csv"
|
||||||
|
charts_dir = conc_dir / "charts"
|
||||||
|
|
||||||
|
h = doc.add_heading("4.1 物理模型反演浓度统计与分析", level=2)
|
||||||
|
self._style_heading(h, level=2)
|
||||||
|
|
||||||
|
fig_num = start_figure_num
|
||||||
|
|
||||||
|
if stats_csv.is_file():
|
||||||
|
try:
|
||||||
|
stats_df = pd.read_csv(stats_csv)
|
||||||
|
table = doc.add_table(rows=1, cols=len(stats_df.columns))
|
||||||
|
table.style = "Table Grid"
|
||||||
|
hdr_cells = table.rows[0].cells
|
||||||
|
for i, col_name in enumerate(stats_df.columns):
|
||||||
|
hdr_cells[i].text = str(col_name)
|
||||||
|
for run in hdr_cells[i].paragraphs[0].runs:
|
||||||
|
run.font.name = self.chinese_font
|
||||||
|
run.font.size = Pt(10)
|
||||||
|
run.font.bold = True
|
||||||
|
run._element.rPr.rFonts.set(qn('w:eastAsia'), self.chinese_font)
|
||||||
|
for _, row_data in stats_df.iterrows():
|
||||||
|
row_cells = table.add_row().cells
|
||||||
|
for i, val in enumerate(row_data):
|
||||||
|
row_cells[i].text = str(val)
|
||||||
|
for run in row_cells[i].paragraphs[0].runs:
|
||||||
|
run.font.name = self.chinese_font
|
||||||
|
run.font.size = Pt(10)
|
||||||
|
run._element.rPr.rFonts.set(qn('w:eastAsia'), self.chinese_font)
|
||||||
|
doc.add_paragraph()
|
||||||
|
except Exception as e:
|
||||||
|
doc.add_paragraph(f"[浓度统计表插入失败: {e}]")
|
||||||
|
else:
|
||||||
|
doc.add_paragraph("[浓度统计表不存在: statistics_summary.csv]")
|
||||||
|
|
||||||
|
if charts_dir.is_dir():
|
||||||
|
image_extensions = ['*.png', '*.jpg', '*.jpeg', '*.tif', '*.tiff']
|
||||||
|
chart_files: List[Path] = []
|
||||||
|
for ext in image_extensions:
|
||||||
|
chart_files.extend(sorted(charts_dir.glob(ext)))
|
||||||
|
for chart_file in chart_files:
|
||||||
|
caption_text = f"图{fig_num} {chart_file.stem} 分布图"
|
||||||
|
if self._add_image_with_caption(doc, str(chart_file), caption_text, width=Inches(5.5)):
|
||||||
|
if all_image_analyses is not None:
|
||||||
|
image_type = "boxplot" if "boxplot" in chart_file.stem.lower() else "distribution"
|
||||||
|
analysis_text = self._analyze_and_cache_image(
|
||||||
|
image_path=chart_file,
|
||||||
|
image_type=image_type,
|
||||||
|
param=chart_file.stem,
|
||||||
|
figure_num=fig_num,
|
||||||
|
)
|
||||||
|
self._add_ai_analysis_paragraph(doc, analysis_text)
|
||||||
|
all_image_analyses.append({
|
||||||
|
"figure_num": fig_num,
|
||||||
|
"param": chart_file.stem,
|
||||||
|
"image_type": image_type,
|
||||||
|
"image_name": chart_file.name,
|
||||||
|
"analysis": analysis_text,
|
||||||
|
})
|
||||||
|
fig_num += 1
|
||||||
|
try:
|
||||||
|
if progress is not None:
|
||||||
|
progress.update(1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
doc.add_paragraph("[浓度图表目录不存在: 9_Concentration/charts/]")
|
||||||
|
|
||||||
|
return fig_num
|
||||||
|
|
||||||
# ==================== 使用示例 ====================
|
# ==================== 使用示例 ====================
|
||||||
|
|
||||||
def generate_full_water_quality_report(
|
def generate_full_water_quality_report(
|
||||||
|
|||||||
Reference in New Issue
Block a user