Compare commits
19 Commits
303f202547
...
600400a4d9
| Author | SHA1 | Date | |
|---|---|---|---|
| 600400a4d9 | |||
| 208e067334 | |||
| 66a181cbe2 | |||
| 3f4cb94929 | |||
| a13b4be6d4 | |||
| 683cf77da5 | |||
| a3e83e846a | |||
| a572b52239 | |||
| 5fe71917a6 | |||
| f99fa63f0e | |||
| 8a7273dd57 | |||
| dd04ee323c | |||
| 836c322b10 | |||
| b88bab57ee | |||
| fc6036efb7 | |||
| 43d56e74d9 | |||
| 650e505ac4 | |||
| 692a88c7bb | |||
| 1740425152 |
@ -14,7 +14,7 @@ from src.gui.panels._step_path_resolver import resolve_subdir
|
|||||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
|
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
|
||||||
QLabel, QCheckBox, QPushButton, QLineEdit,
|
QLabel, QCheckBox, QPushButton, QLineEdit, QComboBox, QSpinBox,
|
||||||
QMessageBox, QFileDialog, QProgressBar,
|
QMessageBox, QFileDialog, QProgressBar,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,12 +33,15 @@ class ReportWorkerThread(QThread):
|
|||||||
finished = pyqtSignal(str)
|
finished = pyqtSignal(str)
|
||||||
error = pyqtSignal(str)
|
error = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, work_dir: str, output_dir: Optional[str], report_title: str, enable_ai: bool):
|
def __init__(self, work_dir: str, output_dir: Optional[str], report_title: str,
|
||||||
|
enable_ai: bool, report_mode: str = 'auto', ai_maps_limit: int = 999):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.work_dir = work_dir
|
self.work_dir = work_dir
|
||||||
self.output_dir = output_dir
|
self.output_dir = output_dir
|
||||||
self.report_title = report_title
|
self.report_title = report_title
|
||||||
self.enable_ai = enable_ai
|
self.enable_ai = enable_ai
|
||||||
|
self.report_mode = report_mode
|
||||||
|
self.ai_maps_limit = ai_maps_limit
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
@ -76,6 +79,8 @@ class ReportWorkerThread(QThread):
|
|||||||
out_path = gen.generate_report(
|
out_path = gen.generate_report(
|
||||||
work_dir=self.work_dir,
|
work_dir=self.work_dir,
|
||||||
report_title=self.report_title or "水质参数反演分析报告",
|
report_title=self.report_title or "水质参数反演分析报告",
|
||||||
|
report_mode=self.report_mode,
|
||||||
|
ai_maps_limit=self.ai_maps_limit,
|
||||||
on_progress=lambda pct, text: self.progress.emit(int(pct), str(text)),
|
on_progress=lambda pct, text: self.progress.emit(int(pct), str(text)),
|
||||||
)
|
)
|
||||||
self.finished.emit(str(out_path))
|
self.finished.emit(str(out_path))
|
||||||
@ -257,6 +262,37 @@ class Step13ReportPanel(QWidget):
|
|||||||
""")
|
""")
|
||||||
execute_layout.addWidget(self.progress_bar)
|
execute_layout.addWidget(self.progress_bar)
|
||||||
|
|
||||||
|
# 报告模式(自动检测 + 可手动纠正)
|
||||||
|
mode_row = QHBoxLayout()
|
||||||
|
mode_row.setContentsMargins(0, 4, 0, 0)
|
||||||
|
mode_row.setSpacing(10)
|
||||||
|
mode_label = QLabel("报告模式:")
|
||||||
|
mode_label.setMinimumWidth(120)
|
||||||
|
mode_label.setMaximumWidth(120)
|
||||||
|
mode_row.addWidget(mode_label)
|
||||||
|
self.report_mode_cb = QComboBox()
|
||||||
|
self.report_mode_cb.addItem("机器学习水质参数反演", "ml")
|
||||||
|
self.report_mode_cb.addItem("水色指数与物理经验公式反演", "formula")
|
||||||
|
self.report_mode_cb.setStyleSheet(common_lineedit_css)
|
||||||
|
mode_row.addWidget(self.report_mode_cb, 1)
|
||||||
|
execute_layout.addLayout(mode_row)
|
||||||
|
|
||||||
|
# AI 专题图分析数量(仅水色指数公式模式生效,ML 模式忽略)
|
||||||
|
ai_maps_row = QHBoxLayout()
|
||||||
|
ai_maps_row.setContentsMargins(0, 0, 0, 0)
|
||||||
|
ai_maps_row.setSpacing(10)
|
||||||
|
ai_maps_label = QLabel("AI分析专题图数:")
|
||||||
|
ai_maps_label.setMinimumWidth(120)
|
||||||
|
ai_maps_label.setMaximumWidth(120)
|
||||||
|
ai_maps_row.addWidget(ai_maps_label)
|
||||||
|
self.ai_maps_spin = QSpinBox()
|
||||||
|
self.ai_maps_spin.setRange(0, 999)
|
||||||
|
self.ai_maps_spin.setValue(999)
|
||||||
|
self.ai_maps_spin.setToolTip("公式报告中启用AI分析的分布图数量上限(0=跳过,999=全部分析)")
|
||||||
|
self.ai_maps_spin.setStyleSheet(common_lineedit_css)
|
||||||
|
ai_maps_row.addWidget(self.ai_maps_spin, 1)
|
||||||
|
execute_layout.addLayout(ai_maps_row)
|
||||||
|
|
||||||
action_layout = QHBoxLayout()
|
action_layout = QHBoxLayout()
|
||||||
action_layout.addStretch()
|
action_layout.addStretch()
|
||||||
|
|
||||||
@ -326,13 +362,25 @@ class Step13ReportPanel(QWidget):
|
|||||||
self.work_dir_edit.setText(str(work_dir))
|
self.work_dir_edit.setText(str(work_dir))
|
||||||
|
|
||||||
def _auto_pull_work_dir(self):
|
def _auto_pull_work_dir(self):
|
||||||
"""从主窗口自动同步工作目录到 work_dir_edit(无需用户操作)。"""
|
"""从主窗口自动同步工作目录到 work_dir_edit,并自动检测报告模式。"""
|
||||||
mw = self.main_window
|
mw = self.main_window
|
||||||
if mw is not None and getattr(mw, "work_dir", None):
|
if mw is not None and getattr(mw, "work_dir", None):
|
||||||
wd = str(mw.work_dir)
|
wd = str(mw.work_dir)
|
||||||
cur = self.work_dir_edit.text().strip()
|
cur = self.work_dir_edit.text().strip()
|
||||||
if wd and wd != cur:
|
if wd and wd != cur:
|
||||||
self.work_dir_edit.setText(wd)
|
self.work_dir_edit.setText(wd)
|
||||||
|
# 自动检测报告模式(参考 Step11 逻辑)
|
||||||
|
self._auto_detect_report_mode(wd)
|
||||||
|
|
||||||
|
def _auto_detect_report_mode(self, wd: str):
|
||||||
|
"""根据工作目录内容自动选择报告模式下拉框"""
|
||||||
|
wd_path = Path(wd)
|
||||||
|
ml_dir = wd_path / "9_ML_Prediction"
|
||||||
|
formula_dir = wd_path / "10_WaterIndex_CSV"
|
||||||
|
if ml_dir.is_dir() and list(ml_dir.glob("*.csv")):
|
||||||
|
self.report_mode_cb.setCurrentIndex(0) # ML
|
||||||
|
elif formula_dir.is_dir() and list(formula_dir.glob("*.csv")):
|
||||||
|
self.report_mode_cb.setCurrentIndex(1) # 公式
|
||||||
|
|
||||||
def update_from_config(self, work_dir=None, pipeline=None):
|
def update_from_config(self, work_dir=None, pipeline=None):
|
||||||
"""切入面板时由主窗口统一调用,把当前 work_dir 同步到本面板。
|
"""切入面板时由主窗口统一调用,把当前 work_dir 同步到本面板。
|
||||||
@ -381,6 +429,7 @@ class Step13ReportPanel(QWidget):
|
|||||||
f"未找到可视化目录:\n{viz}\n请先完成流程或生成可视化。",
|
f"未找到可视化目录:\n{viz}\n请先完成流程或生成可视化。",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._report_thread and self._report_thread.isRunning():
|
if self._report_thread and self._report_thread.isRunning():
|
||||||
QMessageBox.information(self, "提示", "报告正在生成中,请稍候。")
|
QMessageBox.information(self, "提示", "报告正在生成中,请稍候。")
|
||||||
return
|
return
|
||||||
@ -394,7 +443,9 @@ class Step13ReportPanel(QWidget):
|
|||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.progress_label.setText("正在准备生成…")
|
self.progress_label.setText("正在准备生成…")
|
||||||
|
|
||||||
self._report_thread = ReportWorkerThread(wd, out, title, enable_ai)
|
report_mode = self.report_mode_cb.currentData()
|
||||||
|
ai_maps_limit = self.ai_maps_spin.value()
|
||||||
|
self._report_thread = ReportWorkerThread(wd, out, title, enable_ai, report_mode, ai_maps_limit)
|
||||||
self._report_thread.progress.connect(self._on_progress, Qt.QueuedConnection)
|
self._report_thread.progress.connect(self._on_progress, Qt.QueuedConnection)
|
||||||
self._report_thread.finished.connect(self._on_finished, Qt.QueuedConnection)
|
self._report_thread.finished.connect(self._on_finished, Qt.QueuedConnection)
|
||||||
self._report_thread.error.connect(self._on_error, Qt.QueuedConnection)
|
self._report_thread.error.connect(self._on_error, Qt.QueuedConnection)
|
||||||
|
|||||||
@ -368,6 +368,8 @@ class WaterQualityReportGenerator:
|
|||||||
return "(Minimax API Key 未配置,请设置 MINIMAX_API_KEY 环境变量)"
|
return "(Minimax API Key 未配置,请设置 MINIMAX_API_KEY 环境变量)"
|
||||||
|
|
||||||
url = self.minimax_base_url
|
url = self.minimax_base_url
|
||||||
|
if not url.endswith("/chat/completions"):
|
||||||
|
url = f"{url.rstrip('/')}/chat/completions"
|
||||||
|
|
||||||
payload: Dict[str, Any] = {
|
payload: Dict[str, Any] = {
|
||||||
"model": self.minimax_text_model,
|
"model": self.minimax_text_model,
|
||||||
@ -433,6 +435,12 @@ class WaterQualityReportGenerator:
|
|||||||
return f"(读取图片失败:{e})"
|
return f"(读取图片失败:{e})"
|
||||||
|
|
||||||
url = self.minimax_base_url
|
url = self.minimax_base_url
|
||||||
|
if not url.endswith("/chat/completions"):
|
||||||
|
url = f"{url.rstrip('/')}/chat/completions"
|
||||||
|
|
||||||
|
# 动态 MIME 类型:PNG 和 JPEG 分别处理,避免 API 400
|
||||||
|
ext = image_path.suffix.lower()
|
||||||
|
mime_type = "image/png" if ext == ".png" else "image/jpeg"
|
||||||
|
|
||||||
payload: Dict[str, Any] = {
|
payload: Dict[str, Any] = {
|
||||||
"model": self.minimax_vision_model,
|
"model": self.minimax_vision_model,
|
||||||
@ -444,7 +452,7 @@ class WaterQualityReportGenerator:
|
|||||||
{"type": "text", "text": user_prompt},
|
{"type": "text", "text": user_prompt},
|
||||||
{
|
{
|
||||||
"type": "image_url",
|
"type": "image_url",
|
||||||
"image_url": {"url": f"data:image/jpeg;base64,{img_b64}"},
|
"image_url": {"url": f"data:{mime_type};base64,{img_b64}"},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@ -739,10 +747,16 @@ class WaterQualityReportGenerator:
|
|||||||
if not self.enable_ai_analysis:
|
if not self.enable_ai_analysis:
|
||||||
return "(AI分析已关闭)"
|
return "(AI分析已关闭)"
|
||||||
|
|
||||||
# 构造统计数据文本
|
# 构造统计数据文本(防御式 .get() 防 KeyError)
|
||||||
stats_text = "水质参数统计摘要:\n"
|
stats_text = "水质参数统计摘要:\n"
|
||||||
for stat in stats_data:
|
for stat in stats_data:
|
||||||
stats_text += f"- {stat['参数']}: 点位数={stat['点位数']}, 范围=[{stat['最小值']}, {stat['最大值']}], 均值={stat['平均值']}, 标准差={stat['标准差']}\n"
|
param = stat.get('参数', stat.get('Parameter', stat.get('name', '未知')))
|
||||||
|
count = stat.get('点位数', stat.get('数量', stat.get('count', '?')))
|
||||||
|
min_v = stat.get('最小值', stat.get('min', stat.get('Min', '?')))
|
||||||
|
max_v = stat.get('最大值', stat.get('max', stat.get('Max', '?')))
|
||||||
|
mean_v = stat.get('平均值', stat.get('mean', stat.get('Mean', '?')))
|
||||||
|
std_v = stat.get('标准差', stat.get('std', stat.get('Std', '?')))
|
||||||
|
stats_text += f"- {param}: 点位数={count}, 范围=[{min_v}, {max_v}], 均值={mean_v}, 标准差={std_v}\n"
|
||||||
|
|
||||||
system = """你是一位水质遥感与统计分析专家。
|
system = """你是一位水质遥感与统计分析专家。
|
||||||
请基于提供的统计数据,给出专业分析:
|
请基于提供的统计数据,给出专业分析:
|
||||||
@ -764,16 +778,16 @@ class WaterQualityReportGenerator:
|
|||||||
parameters: List[str] = None,
|
parameters: List[str] = None,
|
||||||
report_title: str = "水质参数反演分析报告",
|
report_title: str = "水质参数反演分析报告",
|
||||||
output_path: Optional[str] = None,
|
output_path: Optional[str] = None,
|
||||||
|
report_mode: str = 'auto',
|
||||||
|
ai_maps_limit: int = 999,
|
||||||
on_progress=None) -> str:
|
on_progress=None) -> str:
|
||||||
"""
|
"""
|
||||||
生成 Word 报告 - 所有数据均来自工作目录(work_dir)
|
生成 Word 报告(双轨制入口,默认自动检测模式)
|
||||||
可视化图片、统计数据等均从 work_dir/12_visualization 和 work_dir/4_processed_data 中读取
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
on_progress: 可选回调,签名 on_progress(percent: int, text: str)。
|
report_mode: 'auto'→自动检测(参考Step11), 'ml'/'formula'→强制模式
|
||||||
会在进度更新时被调用,用于驱动 Qt QProgressBar/QThread 信号。
|
|
||||||
"""
|
"""
|
||||||
# 设置工作目录(整个流程的核心)
|
# 设置工作目录
|
||||||
if work_dir is not None:
|
if work_dir is not None:
|
||||||
self.work_dir = Path(work_dir)
|
self.work_dir = Path(work_dir)
|
||||||
self.visualization_dir = self.work_dir / "12_visualization"
|
self.visualization_dir = self.work_dir / "12_visualization"
|
||||||
@ -782,28 +796,37 @@ class WaterQualityReportGenerator:
|
|||||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
self.ai_cache_path = self.output_dir / "ollama_image_analyses_cache.json"
|
self.ai_cache_path = self.output_dir / "ollama_image_analyses_cache.json"
|
||||||
|
|
||||||
|
if not self.visualization_dir.exists():
|
||||||
|
raise FileNotFoundError(f"可视化目录不存在: {self.visualization_dir}")
|
||||||
|
|
||||||
if parameters is None:
|
if parameters is None:
|
||||||
parameters = ["Chlorophyll", "COD", "DO", "PH", "Temperature",
|
parameters = ["Chlorophyll", "COD", "DO", "PH", "Temperature",
|
||||||
"spCond", "Turbidity", "TDS", "Cl-", "NO3-N",
|
"spCond", "Turbidity", "TDS", "Cl-", "NO3-N",
|
||||||
"NH3-N", "BGA", "TT"]
|
"NH3-N", "BGA", "TT"]
|
||||||
|
|
||||||
|
# ── 确定管线模式(优先用户选择,否则自动检测)──
|
||||||
|
if report_mode == 'auto':
|
||||||
|
ml_dir = self.work_dir / "9_ML_Prediction"
|
||||||
|
formula_dir = self.work_dir / "10_WaterIndex_CSV"
|
||||||
|
if ml_dir.is_dir() and list(ml_dir.glob("*.csv")):
|
||||||
|
report_mode = 'ml'
|
||||||
|
elif formula_dir.is_dir() and list(formula_dir.glob("*.csv")):
|
||||||
|
report_mode = 'formula'
|
||||||
|
else:
|
||||||
|
report_mode = 'ml'
|
||||||
|
print(f"[报告] 自动检测 → {report_mode}")
|
||||||
|
else:
|
||||||
|
print(f"[报告] 用户选择 → {report_mode}")
|
||||||
|
|
||||||
|
if report_mode == 'formula':
|
||||||
|
return self._generate_formula_report(parameters, report_title, output_path,
|
||||||
|
ai_maps_limit=ai_maps_limit, on_progress=on_progress)
|
||||||
|
else:
|
||||||
|
return self._generate_ml_report(parameters, report_title, output_path, on_progress)
|
||||||
|
|
||||||
|
def _generate_ml_report(self, parameters, report_title, output_path, on_progress):
|
||||||
|
"""机器学习水质参数反演报告"""
|
||||||
vis_dir = self.visualization_dir
|
vis_dir = self.visualization_dir
|
||||||
|
|
||||||
if not vis_dir.exists():
|
|
||||||
raise FileNotFoundError(f"可视化目录不存在: {vis_dir}")
|
|
||||||
|
|
||||||
# ── 管线模式自动检测 ──
|
|
||||||
self._pipeline_mode = self._detect_pipeline_mode(vis_dir, parameters)
|
|
||||||
print(f"[报告] 管线模式: {self._pipeline_mode}")
|
|
||||||
if self._pipeline_mode == "water_index":
|
|
||||||
# 水色指数模式:只保留实际存在的图片类型
|
|
||||||
_available_types = self._get_available_image_types(vis_dir, parameters)
|
|
||||||
self.parameter_images = {
|
|
||||||
p: [f"{p}_{t}.png" for t in _available_types]
|
|
||||||
for p in parameters
|
|
||||||
}
|
|
||||||
print(f"[报告] 水色指数可用图片类型: {_available_types}")
|
|
||||||
|
|
||||||
if output_path is None:
|
if output_path is None:
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
output_path = self.output_dir / f"水质参数反演分析报告_{timestamp}.docx"
|
output_path = self.output_dir / f"水质参数反演分析报告_{timestamp}.docx"
|
||||||
@ -853,9 +876,14 @@ class WaterQualityReportGenerator:
|
|||||||
|
|
||||||
# 按参数生成内容(带编号):参数章节从 5 开始编号
|
# 按参数生成内容(带编号):参数章节从 5 开始编号
|
||||||
base_section_num = 5
|
base_section_num = 5
|
||||||
last_param_section_num = base_section_num + len(parameters) - 1
|
n_params = len(parameters)
|
||||||
for section_num, param in enumerate(parameters, base_section_num):
|
last_param_section_num = base_section_num + n_params - 1
|
||||||
progress.set_description(f"正在分析 {param} 数据 ({section_num - base_section_num + 1}/{len(parameters)})")
|
for s_i, param in enumerate(parameters):
|
||||||
|
section_num = base_section_num + s_i
|
||||||
|
pct = int((s_i + 1) / n_params * 100)
|
||||||
|
if on_progress:
|
||||||
|
try: on_progress(pct, f"ML报告 参数 {s_i+1}/{n_params}: {param}")
|
||||||
|
except Exception: pass
|
||||||
figure_counter = self._add_parameter_section(
|
figure_counter = self._add_parameter_section(
|
||||||
doc,
|
doc,
|
||||||
param,
|
param,
|
||||||
@ -925,6 +953,197 @@ class WaterQualityReportGenerator:
|
|||||||
|
|
||||||
return str(output_path)
|
return str(output_path)
|
||||||
|
|
||||||
|
def _generate_formula_report(self, parameters, report_title, output_path,
|
||||||
|
ai_maps_limit=999, on_progress=None):
|
||||||
|
"""水色指数与物理经验公式反演报告 (Step 10/11)"""
|
||||||
|
from docx.shared import Inches, Pt, Cm, RGBColor
|
||||||
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
|
|
||||||
|
vis_dir = self.visualization_dir
|
||||||
|
# 分布图优先从 12_visualization/distribution_maps 找,再回退 11_Thematic_Map
|
||||||
|
dist_map_dir = vis_dir / "distribution_maps"
|
||||||
|
thematic_dir = dist_map_dir if dist_map_dir.is_dir() else self.work_dir / "11_Thematic_Map"
|
||||||
|
csv_dir = self.work_dir / "10_WaterIndex_CSV"
|
||||||
|
|
||||||
|
if output_path is None:
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
output_path = self.output_dir / f"水色指数反演分析报告_{timestamp}.docx"
|
||||||
|
else:
|
||||||
|
output_path = Path(output_path)
|
||||||
|
|
||||||
|
# 扫描实际数据量:专题图优先匹配 distribution/rendered 命名
|
||||||
|
csv_files = sorted(csv_dir.glob("*.csv")) if csv_dir.is_dir() else []
|
||||||
|
tif_files = []
|
||||||
|
if thematic_dir.is_dir():
|
||||||
|
tif_files = (sorted(thematic_dir.glob("*distribution*.png"))
|
||||||
|
+ sorted(thematic_dir.glob("*rendered*.png"))
|
||||||
|
+ sorted(thematic_dir.glob("*.tif"))
|
||||||
|
+ sorted(thematic_dir.glob("*.png")))
|
||||||
|
# 去重(同名文件可能多次匹配)
|
||||||
|
seen = set()
|
||||||
|
tif_files = [f for f in tif_files if f.name not in seen and not seen.add(f.name)]
|
||||||
|
n_csv = len(csv_files)
|
||||||
|
n_maps = min(len(tif_files), 20)
|
||||||
|
n_csv_pulses = max(1, n_csv // 10) # 每 10 个 CSV 一次脉冲
|
||||||
|
total_steps = 4 + n_csv_pulses + n_maps # 固定章节 + 统计脉冲 + 每张专题图
|
||||||
|
print(f"[公式报告] 数据: {n_csv} CSV, {len(tif_files)} 专题图 → 总步数 {total_steps}")
|
||||||
|
|
||||||
|
progress = self._create_progress(total=total_steps, desc="生成公式报告", on_step=on_progress)
|
||||||
|
_step = 0
|
||||||
|
def _next(text=""):
|
||||||
|
nonlocal _step
|
||||||
|
_step += 1
|
||||||
|
try: progress.update(1)
|
||||||
|
except Exception: pass
|
||||||
|
if on_progress:
|
||||||
|
try: on_progress(int(_step / total_steps * 100), text or f"步骤 {_step}/{total_steps}")
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
doc = Document()
|
||||||
|
section = doc.sections[0]
|
||||||
|
section.page_width = Cm(21)
|
||||||
|
section.page_height = Cm(29.7)
|
||||||
|
section.left_margin = Cm(2.5)
|
||||||
|
section.right_margin = Cm(2.5)
|
||||||
|
section.top_margin = Cm(2.5)
|
||||||
|
section.bottom_margin = Cm(2.5)
|
||||||
|
|
||||||
|
# ── 封面 ──
|
||||||
|
self._add_cover_page(doc)
|
||||||
|
self._add_company_description_page(doc)
|
||||||
|
self._add_data_acquisition_section(doc)
|
||||||
|
self._add_data_processing_section(doc)
|
||||||
|
_next("封面与通用章节")
|
||||||
|
|
||||||
|
# ── 1. 项目背景 ──
|
||||||
|
h1 = doc.add_heading("1 项目背景", level=1)
|
||||||
|
self._style_heading(h1, 1)
|
||||||
|
doc.add_paragraph("本报告基于高光谱遥感影像,通过物理经验公式(水色指数)反演水体关键参数的空间分布。"
|
||||||
|
"涵盖叶绿素、蓝绿藻、浊度、CDOM 等多种水色指标的定量化空间制图。")
|
||||||
|
doc.add_paragraph("数据来源:机载 / 星载高光谱成像仪,经过辐射定标、大气校正、耀斑去除等预处理。")
|
||||||
|
doc.add_page_break()
|
||||||
|
_next("项目背景")
|
||||||
|
|
||||||
|
# ── 2. 影像预处理 ──
|
||||||
|
h1 = doc.add_heading("2 影像预处理", level=1)
|
||||||
|
self._style_heading(h1, 1)
|
||||||
|
self._add_hyperspectral_images_section(doc)
|
||||||
|
doc.add_page_break()
|
||||||
|
_next("影像预处理")
|
||||||
|
|
||||||
|
# ── 3. 水色指数公式列表 ──
|
||||||
|
h1 = doc.add_heading("3 水色指数计算方法", level=1)
|
||||||
|
self._style_heading(h1, 1)
|
||||||
|
if csv_files:
|
||||||
|
doc.add_paragraph(f"本次反演共应用 {n_csv} 个水色指数公式,"
|
||||||
|
f"各公式基于特征波段比值或差分原理计算:")
|
||||||
|
for i, cf in enumerate(csv_files[:30], 1):
|
||||||
|
doc.add_paragraph(f" {i}. {cf.stem}", style='List Number')
|
||||||
|
if n_csv > 30:
|
||||||
|
doc.add_paragraph(f" ... 共 {n_csv} 个公式,详情见统计章节。")
|
||||||
|
else:
|
||||||
|
doc.add_paragraph(f"(未找到水色指数目录: {csv_dir})")
|
||||||
|
doc.add_page_break()
|
||||||
|
|
||||||
|
# ── 4. 指数统计结果(每个 CSV 更新一次进度)──
|
||||||
|
h1 = doc.add_heading("4 水色指数统计结果", level=1)
|
||||||
|
self._style_heading(h1, 1)
|
||||||
|
figure_num = 10
|
||||||
|
if csv_files:
|
||||||
|
stats_rows = []
|
||||||
|
for i_c, cp in enumerate(csv_files):
|
||||||
|
try:
|
||||||
|
df_one = pd.read_csv(cp, sep=',')
|
||||||
|
val_col = df_one.columns[-1]
|
||||||
|
vals = pd.to_numeric(df_one[val_col], errors='coerce').dropna()
|
||||||
|
if len(vals) > 0:
|
||||||
|
stats_rows.append({
|
||||||
|
'参数': Path(cp).stem, '数量': len(vals),
|
||||||
|
'最小值': round(float(vals.min()), 4),
|
||||||
|
'最大值': round(float(vals.max()), 4),
|
||||||
|
'平均值': round(float(vals.mean()), 4),
|
||||||
|
'标准差': round(float(vals.std(ddof=0)), 4) if len(vals) > 1 else 0,
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# 每处理 10 个 CSV 更新进度
|
||||||
|
if (i_c + 1) % max(1, n_csv // 10) == 0 or i_c == n_csv - 1:
|
||||||
|
_next(f"统计 {i_c+1}/{n_csv}")
|
||||||
|
|
||||||
|
if stats_rows:
|
||||||
|
# 统计表
|
||||||
|
table = doc.add_table(rows=1, cols=6, style='Table Grid')
|
||||||
|
for j, h in enumerate(['公式名称', '数据量', '最小值', '最大值', '平均值', '标准差']):
|
||||||
|
table.rows[0].cells[j].text = h
|
||||||
|
for r in stats_rows:
|
||||||
|
cells = table.add_row().cells
|
||||||
|
cells[0].text = r['参数']
|
||||||
|
cells[1].text = str(r['数量'])
|
||||||
|
cells[2].text = str(r['最小值'])
|
||||||
|
cells[3].text = str(r['最大值'])
|
||||||
|
cells[4].text = str(r['平均值'])
|
||||||
|
cells[5].text = str(r['标准差'])
|
||||||
|
doc.add_paragraph()
|
||||||
|
figure_num += 1
|
||||||
|
|
||||||
|
# AI 分析(防御式读取)
|
||||||
|
stats_data = [{
|
||||||
|
'参数': r['参数'], '点位数': r['数量'],
|
||||||
|
'最小值': str(r['最小值']), '最大值': str(r['最大值']),
|
||||||
|
'平均值': str(r['平均值']), '标准差': str(r['标准差']),
|
||||||
|
} for r in stats_rows]
|
||||||
|
param_names = [r['参数'] for r in stats_rows]
|
||||||
|
analysis = self._analyze_statistics(stats_data, param_names)
|
||||||
|
self._add_ai_analysis_paragraph(doc, analysis)
|
||||||
|
else:
|
||||||
|
doc.add_paragraph("未能解析水色指数 CSV 数据。")
|
||||||
|
else:
|
||||||
|
doc.add_paragraph(f"(未找到水色指数目录: {csv_dir})")
|
||||||
|
doc.add_page_break()
|
||||||
|
|
||||||
|
# ── 5. 指数空间分布专题图(每张更新进度)──
|
||||||
|
h1 = doc.add_heading("5 水色指数空间分布专题图", level=1)
|
||||||
|
self._style_heading(h1, 1)
|
||||||
|
figure_num += 1
|
||||||
|
maps_found = 0
|
||||||
|
if tif_files:
|
||||||
|
n_show = min(len(tif_files), 20)
|
||||||
|
for i_t, tf in enumerate(tif_files[:n_show]):
|
||||||
|
try:
|
||||||
|
param_name = tf.stem.split('_')[0] if '_' in tf.stem else tf.stem
|
||||||
|
caption = f"图{figure_num} {param_name} 空间分布图"
|
||||||
|
if self._add_image_with_caption(doc, str(tf), caption, width=Inches(5.5)):
|
||||||
|
if self.enable_ai_analysis and i_t < ai_maps_limit:
|
||||||
|
ai_text = self._analyze_and_cache_image(
|
||||||
|
image_path=tf, image_type="distribution",
|
||||||
|
param=param_name, figure_num=figure_num)
|
||||||
|
self._add_ai_analysis_paragraph(doc, ai_text)
|
||||||
|
figure_num += 1
|
||||||
|
maps_found += 1
|
||||||
|
except Exception as e:
|
||||||
|
doc.add_paragraph(f"[专题图插入失败: {tf.name} — {e}]")
|
||||||
|
_next(f"专题图 {i_t+1}/{n_show}")
|
||||||
|
if len(tif_files) > 20:
|
||||||
|
doc.add_paragraph(f"... 共 {len(tif_files)} 张专题图,此处仅展示前 20 张。")
|
||||||
|
else:
|
||||||
|
doc.add_paragraph(f"(未找到专题图目录: {thematic_dir})")
|
||||||
|
if maps_found == 0:
|
||||||
|
doc.add_paragraph("(未找到专题图文件,请确认 Step 11 已完成。)")
|
||||||
|
doc.add_page_break()
|
||||||
|
|
||||||
|
# ── 6. 综合总结 ──
|
||||||
|
h1 = doc.add_heading("6 综合总结", level=1)
|
||||||
|
self._style_heading(h1, 1)
|
||||||
|
doc.add_paragraph("本报告基于高光谱遥感影像的水色指数反演方法,对研究区域内的关键水质参数"
|
||||||
|
"进行了定量化空间制图。各指数的统计结果和空间分布专题图如上所示。")
|
||||||
|
doc.add_paragraph("注意事项:水色指数反演结果为半定量指标,其绝对值可能受大气校正精度、"
|
||||||
|
"水体光学特性复杂性等因素影响。建议结合实测水质数据进行校验。")
|
||||||
|
_next("综合总结")
|
||||||
|
|
||||||
|
doc.save(str(output_path))
|
||||||
|
print(f"[公式报告] 生成完成: {output_path}")
|
||||||
|
return str(output_path)
|
||||||
|
|
||||||
def _add_parameter_section(
|
def _add_parameter_section(
|
||||||
self, doc, param: str, vis_dir: Path, param_index: int = 1,
|
self, doc, param: str, vis_dir: Path, param_index: int = 1,
|
||||||
start_figure_num: int = 1, all_image_analyses: Optional[List[Dict[str, Any]]] = None, progress=None,
|
start_figure_num: int = 1, all_image_analyses: Optional[List[Dict[str, Any]]] = None, progress=None,
|
||||||
@ -1254,130 +1473,106 @@ class WaterQualityReportGenerator:
|
|||||||
run.font.size = Pt(12)
|
run.font.size = Pt(12)
|
||||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), 'SimSun')
|
run._element.rPr.rFonts.set(qn('w:eastAsia'), 'SimSun')
|
||||||
|
|
||||||
# 添加高光谱图像、耀斑区域和去耀斑图像展示
|
|
||||||
self._add_hyperspectral_images_section(doc)
|
|
||||||
|
|
||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
|
|
||||||
def _add_hyperspectral_images_section(self, doc):
|
def _add_hyperspectral_images_section(self, doc):
|
||||||
"""添加高光谱图像、耀斑区域和去耀斑图像展示"""
|
"""添加高光谱图像、耀斑区域和去耀斑图像展示
|
||||||
h = doc.add_heading("3.1 高光谱图像处理过程", level=2)
|
找不到文件的章节直接跳过,不显示占位文字。
|
||||||
self._style_heading(h, level=2)
|
"""
|
||||||
|
|
||||||
work_dir_path = self.work_dir
|
work_dir_path = self.work_dir
|
||||||
vis_dir = self.visualization_dir
|
vis_dir = self.visualization_dir
|
||||||
|
|
||||||
# 0. 航线规划图
|
# 0. 航线规划图(仅搜索专用目录,找不到则整节跳过)
|
||||||
flight_path_img_path = work_dir_path / "12_visualization" / "flight_paths"
|
|
||||||
h3 = doc.add_heading("航线规划:", level=3)
|
|
||||||
self._style_heading(h3, level=3)
|
|
||||||
|
|
||||||
# 查找航线图文件
|
|
||||||
flight_map_files = []
|
flight_map_files = []
|
||||||
if flight_path_img_path.exists():
|
flight_dirs = [
|
||||||
flight_map_files = list(flight_path_img_path.glob("*.png")) + list(flight_path_img_path.glob("*.jpg"))
|
work_dir_path / "12_visualization" / "flight_paths",
|
||||||
|
vis_dir / "flight_paths",
|
||||||
|
]
|
||||||
|
for d in flight_dirs:
|
||||||
|
if d.exists():
|
||||||
|
flight_map_files = sorted(d.glob("*.png")) + sorted(d.glob("*.jpg"))
|
||||||
|
if flight_map_files:
|
||||||
|
break
|
||||||
|
|
||||||
if flight_map_files:
|
if flight_map_files:
|
||||||
# 使用最新的航线图文件
|
h3 = doc.add_heading("航线规划:", level=3)
|
||||||
latest_flight_map = max(flight_map_files, key=lambda p: p.stat().st_mtime)
|
self._style_heading(h3, level=3)
|
||||||
success = self._add_image_with_caption(doc, str(latest_flight_map), "图3-1 航线规划", width=Inches(5.5))
|
latest = max(flight_map_files, key=lambda p: p.stat().st_mtime)
|
||||||
|
if self._add_image_with_caption(doc, str(latest), "航线规划", width=Inches(5.5)):
|
||||||
if success:
|
if self.enable_ai_analysis:
|
||||||
# AI 分析航线规划图
|
self._add_ai_analysis_paragraph(doc,
|
||||||
flight_analysis = self._analyze_flight_path_image(str(latest_flight_map))
|
self._analyze_flight_path_image(str(latest)))
|
||||||
self._add_ai_analysis_paragraph(doc, flight_analysis)
|
|
||||||
else:
|
|
||||||
doc.add_paragraph("[航线规划图 - 文件未找到]")
|
|
||||||
|
|
||||||
# 1. 高光谱原始图像
|
# 1. 高光谱原始图像
|
||||||
hyperspectral_img_path = work_dir_path / "1_water_mask" / "hsi_preview.png"
|
hsi_path = work_dir_path / "1_water_mask" / "hsi_preview.png"
|
||||||
h3 = doc.add_heading("高光谱原始影像:", level=3)
|
if hsi_path.exists():
|
||||||
self._style_heading(h3, level=3)
|
h3 = doc.add_heading("高光谱原始影像:", level=3)
|
||||||
if hyperspectral_img_path.exists():
|
self._style_heading(h3, level=3)
|
||||||
self._add_image_with_caption(doc, str(hyperspectral_img_path), "图3-2 高光谱原始影像", width=Inches(5.5))
|
self._add_image_with_caption(doc, str(hsi_path), "高光谱原始影像", width=Inches(5.5))
|
||||||
else:
|
|
||||||
doc.add_paragraph("[高光谱原始影像 - 文件未找到]")
|
|
||||||
|
|
||||||
# 2. 水体掩膜叠加图
|
# 2. 水体掩膜叠加图
|
||||||
water_mask_overlay_path = work_dir_path / "1_water_mask" / "water_mask_overlay.png"
|
wm_path = work_dir_path / "1_water_mask" / "water_mask_overlay.png"
|
||||||
h3 = doc.add_heading("水体区域识别:", level=3)
|
if wm_path.exists():
|
||||||
self._style_heading(h3, level=3)
|
h3 = doc.add_heading("水体区域识别:", level=3)
|
||||||
if water_mask_overlay_path.exists():
|
self._style_heading(h3, level=3)
|
||||||
success = self._add_image_with_caption(doc, str(water_mask_overlay_path),
|
if self._add_image_with_caption(doc, str(wm_path),
|
||||||
"图3-3 水体区域识别(蓝色半透明区域为水域)",
|
"水体区域识别(蓝色半透明区域为水域)",
|
||||||
width=Inches(5.5))
|
width=Inches(5.5)):
|
||||||
if success:
|
if self.enable_ai_analysis:
|
||||||
water_analysis = self._analyze_water_mask_overlay(str(water_mask_overlay_path))
|
self._add_ai_analysis_paragraph(doc,
|
||||||
self._add_ai_analysis_paragraph(doc, water_analysis)
|
self._analyze_water_mask_overlay(str(wm_path)))
|
||||||
else:
|
|
||||||
doc.add_paragraph("[水体区域识别图 - 文件未找到]")
|
|
||||||
|
|
||||||
doc.add_paragraph()
|
# 3. 耀斑区域
|
||||||
|
glint_dirs = [vis_dir / "glint_deglint_previews",
|
||||||
|
work_dir_path / "2_Glint_Detection"]
|
||||||
|
glint_img = None
|
||||||
|
for d in glint_dirs:
|
||||||
|
if d.exists():
|
||||||
|
cands = sorted(d.glob("*glint*.png")) + sorted(d.glob("*severe*.png"))
|
||||||
|
if cands:
|
||||||
|
glint_img = cands[0]
|
||||||
|
break
|
||||||
|
if glint_img:
|
||||||
|
h3 = doc.add_heading("耀斑区域识别结果:", level=3)
|
||||||
|
self._style_heading(h3, level=3)
|
||||||
|
if self._add_image_with_caption(doc, str(glint_img), "耀斑区域识别结果", width=Inches(5.5)):
|
||||||
|
if self.enable_ai_analysis:
|
||||||
|
self._add_ai_analysis_paragraph(doc,
|
||||||
|
self._analyze_glint_distribution_with_ai(str(glint_img)))
|
||||||
|
|
||||||
# 2. 耀斑区域
|
# 4. 去除耀斑后的图像
|
||||||
glint_img_path = vis_dir / "glint_deglint_previews" / "glint_severe_glint_area_preview.png"
|
deglint_img = None
|
||||||
h3 = doc.add_heading("耀斑区域识别结果:", level=3)
|
for d in glint_dirs:
|
||||||
self._style_heading(h3, level=3)
|
if d.exists():
|
||||||
if glint_img_path.exists():
|
cands = sorted(d.glob("*deglint*.png"))
|
||||||
self._add_image_with_caption(doc, str(glint_img_path), "图3-4 耀斑区域识别结果", width=Inches(5.5))
|
if cands:
|
||||||
else:
|
deglint_img = cands[0]
|
||||||
# 尝试查找其他可能的耀斑预览图
|
break
|
||||||
glint_files = list(vis_dir.glob("glint_deglint_previews/*glint*.png"))
|
deglint_dir2 = work_dir_path / "3_deglint"
|
||||||
if glint_files:
|
if deglint_img is None and deglint_dir2.exists():
|
||||||
glint_img_path = glint_files[0]
|
cands = sorted(deglint_dir2.glob("*.png"))
|
||||||
self._add_image_with_caption(doc, str(glint_img_path), "图3-4 耀斑区域识别结果", width=Inches(5.5))
|
if cands:
|
||||||
else:
|
deglint_img = cands[0]
|
||||||
doc.add_paragraph("[耀斑区域识别结果 - 文件未找到]")
|
if deglint_img:
|
||||||
|
h3 = doc.add_heading("去除耀斑后的影像:", level=3)
|
||||||
|
self._style_heading(h3, level=3)
|
||||||
|
self._add_image_with_caption(doc, str(deglint_img), "去除耀斑后的影像", width=Inches(5.5))
|
||||||
|
|
||||||
doc.add_paragraph()
|
# 5. 采样点分布图(找不到则整节跳过)
|
||||||
|
|
||||||
# 3. 去除耀斑后的图像
|
|
||||||
deglint_img_path = vis_dir / "glint_deglint_previews" / "deglint_deglint_image_preview.png"
|
|
||||||
h3 = doc.add_heading("去除耀斑后的影像:", level=3)
|
|
||||||
self._style_heading(h3, level=3)
|
|
||||||
if deglint_img_path.exists():
|
|
||||||
self._add_image_with_caption(doc, str(deglint_img_path), "图3-5 去除耀斑后的高光谱影像", width=Inches(5.5))
|
|
||||||
else:
|
|
||||||
# 尝试查找其他去耀斑预览图
|
|
||||||
deglint_files = list(vis_dir.glob("glint_deglint_previews/*deglint*.png"))
|
|
||||||
if deglint_files:
|
|
||||||
deglint_img_path = deglint_files[0]
|
|
||||||
self._add_image_with_caption(doc, str(deglint_img_path), "图3-5 去除耀斑后的影像", width=Inches(5.5))
|
|
||||||
else:
|
|
||||||
doc.add_paragraph("[去除耀斑后的影像 - 文件未找到]")
|
|
||||||
|
|
||||||
doc.add_paragraph()
|
|
||||||
|
|
||||||
# 4. AI分析耀斑位置分布
|
|
||||||
|
|
||||||
self._style_heading(h3, level=3)
|
|
||||||
glint_analysis = self._analyze_glint_distribution_with_ai(
|
|
||||||
str(glint_img_path) if 'glint_img_path' in locals() and Path(str(glint_img_path)).exists() else None,
|
|
||||||
str(hyperspectral_img_path) if hyperspectral_img_path.exists() else None
|
|
||||||
)
|
|
||||||
self._add_ai_analysis_paragraph(doc, glint_analysis)
|
|
||||||
|
|
||||||
# 5. 采样点分布图
|
|
||||||
sampling_map_dir = vis_dir / "sampling_maps"
|
sampling_map_dir = vis_dir / "sampling_maps"
|
||||||
h3 = doc.add_heading("采样点分布:", level=3)
|
|
||||||
self._style_heading(h3, level=3)
|
|
||||||
|
|
||||||
# 查找采样点分布图文件
|
|
||||||
sampling_map_files = []
|
sampling_map_files = []
|
||||||
if sampling_map_dir.exists():
|
if sampling_map_dir.exists():
|
||||||
sampling_map_files = list(sampling_map_dir.glob("*.png")) + list(sampling_map_dir.glob("*.jpg"))
|
sampling_map_files = (list(sampling_map_dir.glob("*.png"))
|
||||||
|
+ list(sampling_map_dir.glob("*.jpg")))
|
||||||
|
|
||||||
if sampling_map_files:
|
if sampling_map_files:
|
||||||
# 使用最新的采样点分布图文件
|
h3 = doc.add_heading("采样点分布:", level=3)
|
||||||
|
self._style_heading(h3, level=3)
|
||||||
latest_sampling_map = max(sampling_map_files, key=lambda p: p.stat().st_mtime)
|
latest_sampling_map = max(sampling_map_files, key=lambda p: p.stat().st_mtime)
|
||||||
success = self._add_image_with_caption(doc, str(latest_sampling_map), "图3-6 采样点分布图", width=Inches(5.5))
|
if self._add_image_with_caption(doc, str(latest_sampling_map), "采样点分布图", width=Inches(5.5)):
|
||||||
|
if self.enable_ai_analysis:
|
||||||
if success:
|
self._add_ai_analysis_paragraph(doc,
|
||||||
# AI 分析采样点分布图
|
self._analyze_sampling_distribution(str(latest_sampling_map)))
|
||||||
sampling_analysis = self._analyze_sampling_distribution(str(latest_sampling_map))
|
|
||||||
self._add_ai_analysis_paragraph(doc, sampling_analysis)
|
|
||||||
else:
|
|
||||||
doc.add_paragraph("[采样点分布图 - 文件未找到]")
|
|
||||||
|
|
||||||
def _analyze_glint_distribution_with_ai(self, glint_img_path: str = None, original_img_path: str = None) -> str:
|
def _analyze_glint_distribution_with_ai(self, glint_img_path: str = None, original_img_path: str = None) -> str:
|
||||||
"""使用AI分析耀斑的位置分布"""
|
"""使用AI分析耀斑的位置分布"""
|
||||||
@ -1724,7 +1919,11 @@ class WaterQualityReportGenerator:
|
|||||||
row_cells = table.add_row().cells
|
row_cells = table.add_row().cells
|
||||||
for j, (col_name, hdr_name) in enumerate(header_map.items()):
|
for j, (col_name, hdr_name) in enumerate(header_map.items()):
|
||||||
row_cells[j].text = str(row_dict.get(col_name, ''))
|
row_cells[j].text = str(row_dict.get(col_name, ''))
|
||||||
stats_data = [{'参数': r['参数'], '点位数': r['数量']} for r in stats_rows]
|
stats_data = [{
|
||||||
|
'参数': r['参数'], '点位数': r['数量'],
|
||||||
|
'最小值': str(r['最小值']), '最大值': str(r['最大值']),
|
||||||
|
'平均值': str(r['平均值']), '标准差': str(r['标准差']),
|
||||||
|
} for r in stats_rows]
|
||||||
else:
|
else:
|
||||||
# ML 模式:逐列统计
|
# ML 模式:逐列统计
|
||||||
stats_data = []
|
stats_data = []
|
||||||
@ -1780,10 +1979,10 @@ class WaterQualityReportGenerator:
|
|||||||
doc.add_paragraph() # 表格和热力图之间的空行
|
doc.add_paragraph() # 表格和热力图之间的空行
|
||||||
|
|
||||||
# 2. 添加相关性热力图(放在表格下方)—— 仅 ML 模式
|
# 2. 添加相关性热力图(放在表格下方)—— 仅 ML 模式
|
||||||
|
heatmap_path = vis_dir / "correlation_heatmap.png"
|
||||||
if getattr(self, '_pipeline_mode', 'ml') == 'ml':
|
if getattr(self, '_pipeline_mode', 'ml') == 'ml':
|
||||||
h3 = doc.add_heading("4.2 水质参数相关性分析", level=2)
|
h3 = doc.add_heading("4.2 水质参数相关性分析", level=2)
|
||||||
self._style_heading(h3, level=2)
|
self._style_heading(h3, level=2)
|
||||||
heatmap_path = vis_dir / "correlation_heatmap.png"
|
|
||||||
figure_num = start_figure_num
|
figure_num = start_figure_num
|
||||||
if heatmap_path.exists():
|
if heatmap_path.exists():
|
||||||
try:
|
try:
|
||||||
@ -1823,7 +2022,9 @@ class WaterQualityReportGenerator:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
return start_figure_num + (1 if heatmap_path.exists() else 0)
|
heatmap_added = 1 if (getattr(self, '_pipeline_mode', 'ml') == 'ml'
|
||||||
|
and heatmap_path.exists()) else 0
|
||||||
|
return start_figure_num + heatmap_added
|
||||||
|
|
||||||
def _add_physical_inversion_section(
|
def _add_physical_inversion_section(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Reference in New Issue
Block a user