@ -368,6 +368,8 @@ class WaterQualityReportGenerator:
return " ( Minimax API Key 未配置,请设置 MINIMAX_API_KEY 环境变量)"
url = self . minimax_base_url
if not url . endswith ( " /chat/completions " ) :
url = f " { url . rstrip ( ' / ' ) } /chat/completions "
payload : Dict [ str , Any ] = {
" model " : self . minimax_text_model ,
@ -433,6 +435,12 @@ class WaterQualityReportGenerator:
return f " (读取图片失败: { e } ) "
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 ] = {
" model " : self . minimax_vision_model ,
@ -444,7 +452,7 @@ class WaterQualityReportGenerator:
{ " type " : " text " , " text " : user_prompt } ,
{
" 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 :
return " ( AI分析已关闭) "
# 构造统计数据文本
# 构造统计数据文本(防御式 .get() 防 KeyError)
stats_text = " 水质参数统计摘要: \n "
for stat in stats_data :
stats_text + = f " - { stat [ ' 参数 ' ] } : 点位数= { stat [ ' 点位数 ' ] } , 范围=[ { sta t[ ' 最小值 ' ] } , { stat [ ' 最大值 ' ] } ], 均值= { sta t[ ' 平均值 ' ] } , 标准差= { stat [ ' 标准差 ' ] } \n "
param = stat . get ( ' 参数 ' , stat . ge t( ' Parameter ' , stat . ge t( ' 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 = """ 你是一位水质遥感与统计分析专家。
请基于提供的统计数据,给出专业分析:
@ -764,16 +778,16 @@ class WaterQualityReportGenerator:
parameters : List [ str ] = None ,
report_title : str = " 水质参数反演分析报告 " ,
output_path : Optional [ str ] = None ,
report_mode : str = ' auto ' ,
ai_maps_limit : int = 999 ,
on_progress = None ) - > str :
"""
生成 Word 报告 - 所有数据均来自工作目录( work_dir )
可视化图片、统计数据等均从 work_dir/12_visualization 和 work_dir/4_processed_data 中读取
生成 Word 报告(双轨制入口,默认自动检测模式 )
Args:
on_progress: 可选回调,签名 on_progress(percent: int, text: str)。
会在进度更新时被调用,用于驱动 Qt QProgressBar/QThread 信号。
report_mode: ' auto ' →自动检测(参考Step11), ' ml ' / ' formula ' →强制模式
"""
# 设置工作目录(整个流程的核心)
# 设置工作目录
if work_dir is not None :
self . work_dir = Path ( work_dir )
self . visualization_dir = self . work_dir / " 12_visualization "
@ -781,29 +795,38 @@ class WaterQualityReportGenerator:
self . output_dir = self . visualization_dir
self . output_dir . mkdir ( parents = True , exist_ok = True )
self . ai_cache_path = self . output_dir / " ollama_image_analyses_cache.json "
if parameters is None :
parameters = [ " Chlorophyll " , " COD " , " DO " , " PH " , " Temperature " ,
" spCond " , " Turbidity " , " TDS " , " Cl- " , " NO3-N " ,
" NH3-N " , " BGA " , " TT " ]
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 not self . visualization_dir . exists ( ) :
raise FileNotFoundError ( f " 可视化目录不存在: { self . visualization_dir } " )
if parameters is None :
parameters = [ " Chlorophyll " , " COD " , " DO " , " PH " , " Temperature " ,
" spCond " , " Turbidity " , " TDS " , " Cl- " , " NO3-N " ,
" 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
if output_path is None :
timestamp = datetime . now ( ) . strftime ( " % Y % m %d _ % H % M % S " )
output_path = self . output_dir / f " 水质参数反演分析报告_ { timestamp } .docx "
@ -853,9 +876,14 @@ class WaterQualityReportGenerator:
# 按参数生成内容(带编号):参数章节从 5 开始编号
base_section_num = 5
last _param_section_num = base_section_num + len ( parameters ) - 1
for section_num , param in enumerate ( parameters , base_section_num ) :
progress . set_description ( f " 正在分析 { param } 数据 ( { section_num - base_section_num + 1 } / { len ( parameters ) } ) " )
n _params = len ( parameters )
last_param_section_num = base_section_num + n_params - 1
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 (
doc ,
param ,
@ -924,7 +952,198 @@ class WaterQualityReportGenerator:
print ( f " ✅ Word报告生成完成: { 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 (
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 ,
@ -1254,130 +1473,106 @@ class WaterQualityReportGenerator:
run . font . size = Pt ( 12 )
run . _element . rPr . rFonts . set ( qn ( ' w:eastAsia ' ) , ' SimSun ' )
# 添加高光谱图像、耀斑区域和去耀斑图像展示
self . _add_hyperspectral_images_section ( doc )
doc . add_page_break ( )
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
vis_dir = self . visualization_dir
# 0. 航线规划图
flight_path_img_path = work_dir_path / " 12_visualization " / " flight_paths "
h3 = doc . add_heading ( " 航线规划: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
# 查找航线图文件
# 0. 航线规划图(仅搜索专用目录,找不到则整节跳过)
flight_map_files = [ ]
if flight_path_img_path . exists ( ) :
flight_map_files = list ( flight_path_img_path . glob ( " *.png " ) ) + list ( flight_path_img_path . glob ( " *.jpg " ) )
flight_dirs = [
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 :
# 使用最新的航线图文件
latest_flight_map = max ( flight_map_files , key = lambda p : p . stat ( ) . st_mtime )
succ ess = self . _add_image_with_caption ( doc , str ( latest_flight_map ) , " 图3-1 航线规划 " , width = Inches ( 5.5 ) )
if succes s:
# AI 分析航线规划图
flight_analysis = self . _analyze_flight_path_image ( str ( latest_flight_map ) )
self . _add_ai_analysis_paragraph ( doc , flight_analysis )
else :
doc . add_paragraph ( " [航线规划图 - 文件未找到] " )
h3 = doc . add_heading ( " 航线规划: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
lat est = 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 self . enable_ai_analysi s:
self . _add_ai_analysis_paragraph ( doc ,
self . _analyze_flight_path_image ( str ( latest) ) )
# 1. 高光谱原始图像
hyperspectral_img _path = work_dir_path / " 1_water_mask " / " hsi_preview.png "
h3 = doc . add_heading ( " 高光谱原始影像: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
if hyperspectral_img_path . exists ( ) :
self . _add_image_with_caption ( doc , str ( hyperspectral_img _path ) , " 图3-2 高光谱原始影像" , width = Inches ( 5.5 ) )
else :
doc . add_paragraph ( " [高光谱原始影像 - 文件未找到] " )
hsi _path = work_dir_path / " 1_water_mask " / " hsi_preview.png "
if hsi_path . exists ( ) :
h3 = doc . add_heading ( " 高光谱原始影像: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
self . _add_image_with_caption ( doc , str ( hsi _path ) , " 高光谱原始影像 " , width = Inches ( 5.5 ) )
# 2. 水体掩膜叠加图
water_mask_overlay _path = work_dir_path / " 1_water_mask " / " water_mask_overlay.png "
h3 = doc . add_heading ( " 水体区域识别: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
if water_mask_overlay_path . exists ( ) :
success = self . _add_image_with_caption ( doc , str ( water_mask_overlay _path ) ,
" 图3-3 水体区域识别(蓝色半透明区域为水域)" ,
width = Inches ( 5.5 ) )
if succes s:
water_analysis = self . _analyze_water_mask_overlay ( str ( water_mask_overlay_path ) )
self . _add_ai_analysis_paragraph ( doc , water_analysis )
else :
doc . add_paragraph ( " [水体区域识别图 - 文件未找到] " )
wm _path = work_dir_path / " 1_water_mask " / " water_mask_overlay.png "
if wm_path . exists ( ) :
h3 = doc . add_heading ( " 水体区域识别: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
if self . _add_image_with_caption ( doc , str ( wm _path ) ,
" 水体区域识别(蓝色半透明区域为水域) " ,
width = Inches ( 5.5 ) ) :
if self . enable_ai_analysi s:
self . _add_ai_analysis_paragraph ( doc ,
self . _analyze_water_mask_overlay ( str ( wm_path ) ) )
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 . 耀斑区域
glint_img_path = vis_dir / " glint_deglint_previews " / " glint_severe_glint_area_preview.png "
h3 = doc . add_heading ( " 耀斑区域识别结果: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
if glint_img_path . exists ( ) :
self . _add_image_with_caption ( doc , str ( glint_img_path ) , " 图3-4 耀斑区域识别结果 " , width = Inches ( 5.5 ) )
else :
# 尝试查找其他可能的耀斑预览图
glint_files = list ( vis_dir . glob ( " glint _deglint_previews/*glint*.png " ) )
if glint_files :
glint_img_path = glint_files [ 0 ]
self . _add_image_with_caption ( doc , str ( glint_img_path ) , " 图3-4 耀斑区域识别结果 " , width = Inches ( 5.5 ) )
else :
doc . add_paragraph ( " [耀斑区域识别结果 - 文件未找到] " )
# 4 . 去除耀斑后的图像
de glint_img = None
for d in glint_dirs :
if d . exists ( ) :
cands = sorted ( d . glob ( " *deglint*.png " ) )
if cands :
deglint_img = cands [ 0 ]
break
de glint_dir2 = work_dir_path / " 3 _deglint"
if de glint_img is None and deglint_dir2 . exists ( ) :
cands = sorted ( deglint_dir2 . glob ( " *.png " ) )
if cands :
deglint_img = cands [ 0 ]
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 ( )
# 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. 采样点分布图
# 5. 采样点分布图(找不到则整节跳过)
sampling_map_dir = vis_dir / " sampling_maps "
h3 = doc . add_heading ( " 采样点分布: " , level = 3 )
self . _style_heading ( h3 , level = 3 )
# 查找采样点分布图文件
sampling_map_files = [ ]
if sampling_map_dir . exists ( ) :
sampling_map_files = list ( sampling_map_dir . glob ( " *.png " ) ) + list ( sampling_map_dir . glob ( " *.j pg " ) )
sampling_map_files = ( list ( sampling_map_dir . glob ( " *.pn g " ) )
+ list ( sampling_map_dir . glob ( " *.jpg " ) ) )
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 )
success = self . _add_image_with_caption ( doc , str ( latest_sampling_map ) , " 图3-6 采样点分布图" , width = Inches ( 5.5 ) )
if success :
# AI 分析采样点分布图
sampling_analysis = self . _analyze_sampling_distribution ( str ( latest_sampling_map ) )
self . _add_ai_analysis_paragraph ( doc , sampling_analysis )
else :
doc . add_paragraph ( " [采样点分布图 - 文件未找到] " )
if self . _add_image_with_caption ( doc , str ( latest_sampling_map ) , " 采样点分布图 " , width = Inches ( 5.5 ) ) :
if self . enable_ai_analysis :
self . _add_ai_analysis_paragraph ( doc ,
self . _analyze_sampling_distribution ( str ( latest_sampling_map ) ) )
def _analyze_glint_distribution_with_ai ( self , glint_img_path : str = None , original_img_path : str = None ) - > str :
""" 使用AI分析耀斑的位置分布 """
@ -1724,7 +1919,11 @@ class WaterQualityReportGenerator:
row_cells = table . add_row ( ) . cells
for j , ( col_name , hdr_name ) in enumerate ( header_map . items ( ) ) :
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 :
# ML 模式:逐列统计
stats_data = [ ]
@ -1780,10 +1979,10 @@ class WaterQualityReportGenerator:
doc . add_paragraph ( ) # 表格和热力图之间的空行
# 2. 添加相关性热力图(放在表格下方)—— 仅 ML 模式
heatmap_path = vis_dir / " correlation_heatmap.png "
if getattr ( self , ' _pipeline_mode ' , ' ml ' ) == ' ml ' :
h3 = doc . add_heading ( " 4.2 水质参数相关性分析 " , level = 2 )
self . _style_heading ( h3 , level = 2 )
heatmap_path = vis_dir / " correlation_heatmap.png "
figure_num = start_figure_num
if heatmap_path . exists ( ) :
try :
@ -1823,7 +2022,9 @@ class WaterQualityReportGenerator:
pass
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 (
self ,