diff --git a/.gitignore b/.gitignore index 2e6be2a..d7fbf8d 100644 --- a/.gitignore +++ b/.gitignore @@ -155,3 +155,41 @@ tmp/ *.bak *.backup *~ + +# ============================================================ +# 不应进入版本控制的文件类型 +# ============================================================ + +# Qwen Code 用户配置(个人环境,每次 clone 都不同) +.qwen/settings.json +.qwen/settings.json.orig + +# Qwen Code 自动生成的 skill 文件(每次会话重新生成) +.qwen/skills/ + +# GUI 运行时生成的文件 +src/gui/scaler_params.pkl +src/gui/crash_dump.txt + +# 临时/调试脚本(根目录) +降采样光谱.py +1.py +tset.py + +# 报告与文档(本地工作产物) +封装问题分析报告.md +软件说明.md +软件说明2.md + +# 数据子目录中非 .gitkeep 的生成文件 +data/sub/waterindex*.csv +data/sub/waterindex*.xlsx +data/sub/png/watermask.png + +# 图标文件(仅需保留 vector/svg,删除像素图标压缩包副本) +data/icons-1/*.ico +data/icons/*.png +data/icons/word/*.png + +# 旧版脚手架(遗留实验代码) +new/ diff --git a/.qwen/settings.json b/.qwen/settings.json deleted file mode 100644 index 15395fd..0000000 --- a/.qwen/settings.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(\"c:\\users\\duxin\\appdata\\local\\programs\\python\\python311\\python.exe\" *)", - "Bash(get-childitem *)", - "Bash(select-object *)", - "Bash(python *)", - "Bash(where *)", - "Bash(conda *)", - "Bash(dir *)", - "Bash(cmd *)", - "Bash(del *)", - "Bash(powershell *)", - "Bash(git *)", - "Bash(type *)", - "Bash(.\\venv\\scripts\\python.exe *)", - "Bash(\"d:\\111\\office\\zhlduijie\\1.wq\\wq_gui\\venv\\scripts\\python.exe\" *)", - "Bash(c:\\users\\duxin\\appdata\\local\\programs\\python\\python311\\python.exe *)", - "Bash(venv\\scripts\\python.exe *)", - "Bash(findstr *)", - "Bash(select-string *)" - ] - }, - "$version": 4 -} \ No newline at end of file diff --git a/.qwen/settings.json.orig b/.qwen/settings.json.orig deleted file mode 100644 index 0fff81c..0000000 --- a/.qwen/settings.json.orig +++ /dev/null @@ -1,24 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(\"c:\\users\\duxin\\appdata\\local\\programs\\python\\python311\\python.exe\" *)", - "Bash(get-childitem *)", - "Bash(select-object *)", - "Bash(python *)", - "Bash(where *)", - "Bash(conda *)", - "Bash(dir *)", - "Bash(cmd *)", - "Bash(del *)", - "Bash(powershell *)", - "Bash(git *)", - "Bash(type *)", - "Bash(.\\venv\\scripts\\python.exe *)", - "Bash(\"d:\\111\\office\\zhlduijie\\1.wq\\wq_gui\\venv\\scripts\\python.exe\" *)", - "Bash(c:\\users\\duxin\\appdata\\local\\programs\\python\\python311\\python.exe *)", - "Bash(venv\\scripts\\python.exe *)", - "Bash(findstr *)" - ] - }, - "$version": 4 -} \ No newline at end of file diff --git a/.qwen/skills/code_replacement_state_audit/SKILL.md b/.qwen/skills/code_replacement_state_audit/SKILL.md deleted file mode 100644 index 79659a7..0000000 --- a/.qwen/skills/code_replacement_state_audit/SKILL.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -name: 代码替换请求的现状审计 -description: 处理用户"代码替换/新增"指令时,先审计磁盘真实状态再用 ask_user_question 确认——避免覆盖已落盘的高版本代码 -source: auto-skill -extracted_at: '2026-06-03T05:36:58.746Z' ---- - -# 代码替换请求的现状审计 - -## 适用场景 - -用户给出"代码替换"或"按某版本代码新增"指令,但**没有提供与磁盘当前状态对比信息**时。典型触发: - -- 用户贴了一段代码说"请帮我写/替换这个" -- 用户引用某个文档/旧版本/旧 chat 说"按这个来" -- 之前的 `state_snapshot` / `memory` / `git log` 描述可能与磁盘现状不一致 - -## 核心原则 - -**永远不要盲信"用户给的代码是最新版本"**——磁盘上的代码可能已经是更完善的版本(用户或其他 agent 已迭代过)。覆盖 = 丢功能。 - -直接覆盖的代价不一定是显式 bug,也可能是"丢失用户已批准的设计决策"(如 duck-type 探测 / ctx 抽象 / 信号协议 / 二次确认窗 / 错误定位)。 - -## 5 步标准操作 - -### 1. 确认文件存在 - -`glob` 或 `list_directory` 看目标文件是否已存在: - -- 不存在 → 新建 -- 存在 → 进入第 2 步审计 - -### 2. grep 关键符号 + 读关键段 - -- 找"用户贴的代码"里的 3-5 个关键符号(函数名 / 类名 / 关键常量 / import) -- 在磁盘文件里 grep 同样的符号 -- `read_file` 关键段(行号从 grep 结果直接拿) - -### 3. 构造差异对照表 - -列出: - -``` -| 目标文件 | 用户贴的版本 | 磁盘现有版本 | 直接覆盖会丢失 | -``` - -**关键列**:"直接覆盖会丢失什么"——让用户判断成本。具体粒度到"功能模块 / 设计决策 / 防御层 / 入口协议",不要写"代码差异"这种空话。 - -### 4. ask_user_question 让用户拍板 - -3 个标准选项(措辞可调,但**必须给出现状 + 三选一**): - -- **A. 保留现状**(推荐,磁盘已是更新版)—— 直接进 Smoke Test -- **B. 强制覆盖到旧版** —— 写明丢什么 + 备份建议(git stash / 复制到 `_old.py`) -- **C. 混合:只取某段增量** —— 见第 5 步 - -**不要在第 1 次 ask 时就列具体的"哪段增量"**——先让用户在 A/B/C 之间选。如果选 C,再做第 5 步。 - -### 5. 若用户选 C,识别"真正增量" - -对比 1.0 vs 2.0,识别 1.0 真正独有的部分(2.0 没有的): - -- ❌ 排除 1.0 比 2.0 简单的(2.0 是超集 / 工厂分层 / 多了 CLI) -- ❌ 排除 1.0 整体被 2.0 工厂分层超越的(_make_objective vs _build_model + _get_search_space) -- ✅ 关注 1.0 独有的功能层(即使 2.0 不"明显"需要) - -对每个候选增量,再问一次"采纳哪段",让用户具体选(multiSelect=false,一次只选 1 段最稳)。 - -## 落地原则 - -执行"采纳 1.0 某段增量到 2.0"时: - -- **最小化外科手术式编辑**:只动需要动的文件,只改需要改的段 -- **保留 2.0 的设计决策**(duck-type 探测 / ctx 抽象 / 信号协议 / 二次确认窗 / 错误定位) -- **顶部 import 增量用 `replace_all=False` 单点插入**,避免破坏其他 import 顺序 -- **同名变量全链路替换**(如 `self.config` → `clean_config`)要贯穿 ctx 构造 / v2 调用 / v1 fallback,避免双源差异 -- **单步模式不一定要清洗**(不走 panel 完整 config,与清洗器无关) -- **清洗器这种"防患于未然"的代码要给日志**(`self.log_message.emit(f"[清洗器] 已删除 N 个未知 key")`)让运行时可见 - -## 验证三件套 - -落地后必跑: - -1. **AST 语法检查**:`ast.parse(open(p, encoding='utf-8-sig').read())` 对 5 个核心文件 - - 必加 `utf-8-sig`:WQ_GUI 的 water_quality_gui.py line 1 是 BOM,plain `utf-8` 必挂 -2. **关键符号 grep**:确认新代码的关键符号(import / 关键函数调用)都命中,hit 数符合预期 -3. **顶层导入测试**:用 mock PyQt5 + `sys.path.insert(0, 'src/gui/core')`,验证模块整体可加载 - - PyQt5 mock 模板见下方"参考代码" - - Windows 环境调 Python:用 conda env 的 `python.exe` 全路径,不要靠 PATH - -## 反例(不要做) - -- ❌ "按用户贴的代码原封不动写入"——1.0 简化版的覆盖陷阱 -- ❌ "保留 state_snapshot 描述"——state snapshot 可能不准确(写的是意图,磁盘才是事实) -- ❌ "用 git log 反推当前状态"——git log 不能反映工作区未提交改动 -- ❌ "靠 memory 推断当前状态"——memory 可能是 22 天前的(已确认过期) -- ❌ "磁盘和用户给的代码看起来一样就不审计"——一行之差可能就是"防弹层"丢失 - -## 参考代码 - -### PyQt5 mock 模板(worker_thread.py 顶层导入测试) - -```python -import os, sys -os.environ['GDAL_FILENAME_IS_UTF8'] = 'YES' -os.environ['SHAPE_ENCODING'] = 'UTF-8' -sys.path.insert(0, 'src/gui/core') - -import types -pyqt5 = types.ModuleType("PyQt5") -qtc = types.ModuleType("PyQt5.QtCore") -class _QThread: - def __init__(self, *a, **kw): pass -class _Signal: - def __init__(self, *a, **kw): pass -qtc.QThread = _QThread -qtc.pyqtSignal = _Signal -qtc.Qt = type("Qt", (), {"QueuedConnection": 1, "UserRole": 0})() -sys.modules["PyQt5"] = pyqt5 -sys.modules["PyQt5.QtCore"] = qtc - -import worker_thread -# 副作用: check_pipeline_dependencies() 会打印依赖检查日志(可忽略) -``` - -### Windows 上跑 conda env python - -```bat -cmd /c "D:\xxx\anconda\envs\XXX\python.exe D:\path\to\script.py" -``` - -PowerShell 单行 `python -c "..."` 在中文路径 / 双引号 / 单引号嵌套时易翻车,**写临时 .py 文件再用 `cmd /c` 调**最稳。 - -## 案例来源(2026-06-03 WQ_GUI 路线 B MVP) - -- 用户贴 1.0 简化版:300 行 automl_trainer / 简化 worker_thread.run() / 简化 on_run_all_clicked -- 磁盘上 2.0 落盘版:545 行 automl_trainer(_build_model + _get_search_space 工厂 / argparse CLI)/ duck-type 探测 v2 + PipelineContext 抽象 / 完整二次确认窗 / 失败步骤 _focus_step 定位 / [DEPRECATED] stop 保留 -- 1.0 唯一真增量 = **"防弹级参数清洗器"**(method_map 14 项 + inspect.signature 过滤未知 key + has_kwargs 豁免 + 未知 key 数量日志) -- 落地:worker_thread.py:run() 内 set_callback 之后插入 53 行清洗器,self.config 6 处替换为 clean_config -- 验证:5 文件 AST 全通过 + 关键符号 7 项命中 + PyQt5 mock 下 import 成功 -- 净增行数:407 → 457(+50 行) diff --git a/.qwen/skills/facade_kwargs_defense/SKILL.md b/.qwen/skills/facade_kwargs_defense/SKILL.md deleted file mode 100644 index 34f4ba5..0000000 --- a/.qwen/skills/facade_kwargs_defense/SKILL.md +++ /dev/null @@ -1,309 +0,0 @@ ---- -name: PipelineRunner Facade 防御性 kwargs 兜底 -description: WQ_GUI 14 个 stepX_... Facade 方法必须以 **kwargs 收尾——配合 PipelineRunner 调度模式杜绝 "unexpected keyword argument" TypeError -source: auto-skill -extracted_at: '2026-06-04T00:54:50.036Z' ---- - -# PipelineRunner Facade 防御性 kwargs 兜底 - -## 适用场景 - -在 WQ_GUI 中,**任何被 `PipelineRunner` 调用的 14 个 `stepX_...` Facade 方法**(位于 `src/core/water_quality_inversion_pipeline_GUI.py`),其形参表末尾**必须**带 `**kwargs`。触发信号: - -- 用户报错 `TypeError: stepX_xxx() got an unexpected keyword argument 'yyy'` -- 改 PIPELINE_STEPS 的 `requires` 列表 -- 新增 / 重命名一个 step 方法 -- 重构 PipelineRunner 的 `_invoke` 注入逻辑 - -## 核心原则 - -**Facade 的形参表 = 显式声明的形参 + `, **kwargs`**。`kwargs` 必须**严格位于形参表最后**(Python 语法硬要求)。 - -```python -# ✅ 正确 -def step3_remove_glint(self, img_path: str, - method: str = "subtract_nir", - # ... 30+ 业务形参 ... - skip_dependency_check: bool = False, - **kwargs) -> str: - ... - -# ❌ 错误:**kwargs 不能放中间或前面 -def step3_remove_glint(self, img_path, **kwargs, skip_dependency_check): # SyntaxError -``` - -## 为什么需要这层防御 - -`PipelineRunner._invoke`(`src/core/pipeline/runner.py`)会向方法注入两类参数: - -| 层 | 来源 | 形参 key 怎么定 | -|---|---|---| -| **L2** | ctx 字段(按 `spec.requires` 列表) | `_default_param_name(ctx_key)` 默认去 `_path` 后缀 | -| **L3** | `ctx.user_config[step_id]`(14 panel dict 整体) | dict 的 key 原样注入 | - -**L2 触发 TypeError 的真实场景**(2026-06-04 真实发生): - -- `PIPELINE_STEPS.step3.requires = ["img_path", "water_mask_path", "glint_mask_path"]` -- Runner 注入 `kwargs["glint_mask_path"] = ctx.glint_mask_path` -- `step3_remove_glint` 形参表**没有** `glint_mask_path`(虽然业务上耀斑掩膜是在子调用 `GlintRemovalStep.run` 内部用的,Facade 本身不接) -- → **TypeError: step3_remove_glint() got an unexpected keyword argument 'glint_mask_path'** - -**L3 触发 TypeError 的场景**: - -- user_config 14 panel dict 里残留了旧字段名 / 跨 step 串味的字段 -- 任何 `user_config[step_id][k]` 中的 `k` 都会被注入 - -`**kwargs` 一次性解决两类问题。 - -## 14 个 Facade 方法清单(截至 2026-06-04 已全部带 **kwargs) - -| step | method | 形参表闭合示例 | -|---|---|---| -| 1 | `step1_generate_water_mask` | `output_path: Optional[str] = None, **kwargs) -> str:` | -| 2 | `step2_find_glint_area` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 3 | `step3_remove_glint` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 4 | `step4_process_csv` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 5 | `step5_extract_training_spectra` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 5.5 | `step5_5_calculate_water_quality_indices` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 6 | `step6_train_models` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 6.5 | `step6_5_non_empirical_modeling` | `skip_dependency_check: bool = False, **kwargs) -> Dict[str, str]:` | -| 6.75 | `step6_75_custom_regression` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 7 | `step7_generate_sampling_points` | `skip_dependency_check: bool = False, **kwargs) -> str:` | -| 8 | `step8_predict_water_quality` | `skip_dependency_check: bool = False, **kwargs) -> Dict[str, str]:` | -| 8.5 | `step8_5_predict_with_non_empirical_models` | `skip_dependency_check: bool = False, **kwargs) -> Dict[str, str]:` | -| 8.75 | `step8_75_predict_with_custom_regression` | `skip_dependency_check: bool = False, **kwargs) -> Dict[str, str]:` | -| 9 | `step9_generate_distribution_map` | `skip_dependency_check: bool = False, **kwargs) -> str:` | - -## 标准操作 - -### 1. 编辑(最小外科手术式) - -每个方法的最后形参是 `skip_dependency_check: bool = False`,把这一行改成: - -```python - skip_dependency_check: bool = False, **kwargs) -> str: -``` - -**注意缩进必须与原行一致**(13 空格 / 35 空格 / 47 空格 / 48 空格不等,按方法原始缩进)。用 `edit` 工具的 old_string **必须含 docstring 第一行**(`"""步骤X: ..."""`)作唯一标识。 - -### 2. 验证 - -写一个临时校验脚本(项目根目录运行后删掉): - -```python -import ast, re -target = r'D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\core\water_quality_inversion_pipeline_GUI.py' -src = open(target, encoding='utf-8-sig').read() -ast.parse(src) # AST 语法 - -expected = [ - 'step1_generate_water_mask', 'step2_find_glint_area', 'step3_remove_glint', - 'step4_process_csv', 'step5_extract_training_spectra', - 'step5_5_calculate_water_quality_indices', 'step6_train_models', - 'step7_generate_sampling_points', 'step8_predict_water_quality', - 'step9_generate_distribution_map', 'step6_5_non_empirical_modeling', - 'step6_75_custom_regression', 'step8_5_predict_with_non_empirical_models', - 'step8_75_predict_with_custom_regression', -] -pat = re.compile(r'def\s+(\w+)\s*\((?:[^()]|\([^()]*\))*\*\*kwargs\)\s*->\s*[^:]+:', re.DOTALL) -found = set(pat.findall(src)) -print('Missing:', [m for m in expected if m not in found]) -print('Extra :', [m for m in found if m not in expected]) -``` - -期望输出:两个列表都为空。 - -### 3. Windows 执行 - -```bat -cd /d D:\111\office\ZHLduijie\1.WQ\WQ_GUI -py _check.py -del _check.py -``` - -> 必加 `utf-8-sig`:`water_quality_inversion_pipeline_GUI.py` 头部可能含 BOM(`code_replacement_state_audit` skill 里有同样提示)。 - -## ⚠️ 这层防御不能解决什么 - -**` **kwargs` 兜底 ≠ 形参名错位修复**。如果 Runner 注入 `kwargs["training_csv_path"]` 而方法形参是 `csv_path`: - -- ✅ **不会报 TypeError**(`training_csv_path` 被 `**kwargs` 收走) -- ❌ **但 `csv_path` 仍是 None**(方法体内的 `if csv_path is not None: ... else: ...` 走 fallback 分支,可能读 `self.training_csv_path` 哨兵) - -**已知形参名错位的方法**(2026-06-04 已修,commit `64aa5b8`): - -| step | Runner 注入的 ctx key | 方法实际形参 | 实际落地的修复 | -|---|---|---|---| -| step6_5 | `training_csv_path` | `csv_path` | `parameter_map={"training_csv_path": "csv_path"}` | -| step6_75 | (已切到 `indices_path`) | `csv_path` | ⚠️ 见下方"step6_75 路由修复"专题 | -| step8_5 | `models_dir` | `non_empirical_models_dir` | `parameter_map={"models_dir": "non_empirical_models_dir"}` | -| step8_75 | `models_dir` | `custom_regression_dir` | `parameter_map={"models_dir": "custom_regression_dir"}` | - -> **parameter_map** 是 `StepSpec` 已有字段(runner.py:33),作用是把 ctx 字段重命名到方法形参名。**优先用 parameter_map 而非改 requires**——保持 ctx 字段语义清晰(声明式描述上游依赖),形参名是方法私有约定。 - -### step6_75 路由修复(特殊案例,2026-06-04 commit `64aa5b8`) - -`step6_75_custom_regression` **不是简单的 ctx 字段名错位**——方法体内的 fallback 链透露了**真正的数据源是 `indices_path`**: - -```python -# step6_75 形参:csv_path -# 方法体 fallback: -# if csv_path is not None: input_csv = csv_path -# elif self.indices_path is not None: input_csv = self.indices_path # ★ 真相 -``` - -加 `parameter_map` 把 `training_csv_path → csv_path` 看似能跑通,但**实际用错了数据**(training_csv 是 step5 输出,不是 step6_75 想要的 indices CSV)。 - -**正确做法 = 同时改 requires + parameter_map**: - -```python -StepSpec( - step_id="step6_75", method_name="step6_75_custom_regression", - requires=["indices_path"], # ★ 从 training_csv_path 切到 indices_path - produces=["models_dir"], - parameter_map={"indices_path": "csv_path"}, # ★ 同步改 key - description="自定义回归分析", -), -``` - -**配合 `skip_when_missing` 兜底**:若用户没跑 step5_5(`ctx.indices_path` 为 None),runner 自动 skip 整个 step6_75,不会用错位数据静默执行。 - -**判别何时需要"路由切"vs"纯 rename"**: -- 看方法体 fallback 链:fallback 到 `self.indices_path`/`self.deglint_img_path`/其他 ctx 字段名 → **需要改 requires** -- 仅是 key 名字不同,方法体直接用形参 → 只改 parameter_map - -## L2 注入顺序冲突:多个 requires 字段解析到同一形参名 - -### 场景 - -`StepSpec.requires` 里有**多个 ctx 字段**,经过 `_default_param_name` / `parameter_map` 解析后,**会落到同一个方法形参名**。L2 注入是**顺序敏感**的(后者覆盖前者),后注入的会**默默覆盖**前一个的赋值。 - -### 真实案例(2026-06-04 step5 修复) - -业务需求:step5 真正需要 step4 产物 `processed_csv_path`,但**保留 raw `csv_path` 字段**作为 `user_config` 覆盖入口。 - -**❌ 错误的 parameter_map 写法**(用户原方案的隐藏 bug): - -```python -StepSpec( - step_id="step5", method_name="step5_extract_training_spectra", - requires=["deglint_img_path", "processed_csv_path", "csv_path", ...], # raw csv_path 也在 - parameter_map={"processed_csv_path": "csv_path"}, # ★ 只映射了一个 - ... -) -``` - -L2 注入顺序(`runner.py:184-186`): -1. `deglint_img_path` → `kwargs[deglint_img_path] = ctx.deglint_img_path` -2. `processed_csv_path` → `kwargs[csv_path] = ctx.processed_csv_path` ← 主路径生效 -3. `csv_path`(无映射 → 默认)→ `kwargs[csv_path] = ctx.csv_path` ← **后注入 None 覆盖了主路径!** - -**症状**:step5 形参 `csv_path` 拿到的是 raw `ctx.csv_path`(通常是 None),方法体 fallback 到 `self.processed_csv_path`——但这个 fallback 也可能是 None(step4 没跑),step5 内部空跑 → "**静默错误**"。 - -**✅ 修法:parameter_map 双向映射 + 占位名落 **kwargs**: - -```python -parameter_map={ - "processed_csv_path": "csv_path", # 主路径(注入到方法形参) - "csv_path": "_raw_csv_ignored", # 占位(落到 step5 形参列表末尾的 **kwargs) -}, -``` - -注入顺序重排后: -- `processed_csv_path` → `kwargs[csv_path] = ctx.processed_csv_path` ← 主路径 -- `csv_path` → `kwargs[_raw_csv_ignored] = ctx.csv_path` ← 落 **kwargs(被吞) - -step5 形参 `csv_path` 最终拿到 `ctx.processed_csv_path` 的值 ✓。 - -### 验证模板(行为模拟) - -写临时 `_verify_l2_inject.py` 复刻 `runner.py:184-186` 的 L2 注入循环,**不要只靠 AST 静态检查**——parameter_map 的 key 顺序、requires 的字段顺序都是动态的: - -```python -import sys -sys.path.insert(0, r'D:\111\office\ZHLduijie\1.WQ\WQ_GUI') -from src.core.pipeline.context import PipelineContext -from src.core.pipeline.runner import PIPELINE_STEPS - -spec5 = next(s for s in PIPELINE_STEPS if s.step_id == 'step5') - -# 复刻 L2 注入(与 runner.py:184-186 完全一致) -def l2_inject(spec, ctx): - kwargs = {} - for ctx_key in spec.requires: - param_name = spec.parameter_map.get(ctx_key, ctx_key) # ★ 必须原样复刻 - kwargs[param_name] = ctx.get(ctx_key) - return kwargs - -# 关键断言 -ctx = PipelineContext(processed_csv_path='/csv/processed.csv', csv_path='/csv/raw.csv') -kw = l2_inject(spec5, ctx) -assert kw.get('csv_path') == '/csv/processed.csv', \ - f"形参 csv_path 应等于 processed_csv_path, 实际 {kw.get('csv_path')!r}" -assert '_raw_csv_ignored' in kw, "占位名应被注入到 kwargs" -print(f'OK: csv_path 形参 = {kw["csv_path"]!r} (processed, 主路径正确)') -print(f'OK: _raw_csv_ignored 占位 = {kw["_raw_csv_ignored"]!r} (raw, 落 **kwargs 被吞)') -``` - -跑完删掉:`py _verify_l2_inject.py & del _verify_l2_inject.py`(Windows 一行模式)。 - -### 何时需要警惕这个冲突 - -修改 StepSpec 时检查清单(**先看这一段再写 parameter_map**): - -- [ ] **requires 里是否有多于 1 个 ctx 字段,解析后会落到同名方法形参?** 典型撞车: - - 同名字段(如 `processed_csv_path` 和 `csv_path` 都能映射到 `csv_path`) - - 同名 `_default_param_name` 退化(如 `boundary_path` 和 `boundary_shp_path` 默认都映射到 `boundary_path`——但要注意 `_default_param_name` 已废弃去后缀,原样返回 ctx key,所以 `boundary_path` 和 `boundary_shp_path` 默认就会撞 `boundary_path` / `boundary_shp_path` 不会撞,要撞就必须显式 parameter_map) - - 字段名 + parameter_map 重命名撞车 -- [ ] **"主路径"字段在 requires 列表靠前位置**(让后续"备路径"覆盖,但**这不解决冲突**——只要有第二次注入就一定会覆盖) -- [ ] **"备路径"字段**用占位名 `_xxx_ignored` / `_xxx_kwargs_only` 映射,让它落到 **kwargs -- [ ] **确认方法形参表末尾有 `**kwargs`** 兜底(`facade_kwargs_defense` skill 核心要求,已 14/14 落地) - -### 反例(不要做) - -- ❌ "我让 `csv_path` 不在 requires 里就行了"——会**丢失 user_config 覆盖入口**(如果用户想用 raw CSV 而不是 processed) -- ❌ "改 L2 注入循环,让 parameter_map 字段最后注入"——会**改变 runner 通用语义**,影响所有 step 的注入顺序 -- ❌ "加 `if param_name in kwargs: continue` 在 L2 注入里"——隐式"第一次优先"语义,新人读代码摸不着头脑 -- ❌ "用 position in requires 做加权"——把数据语义(哪个字段优先级高)塞到列表顺序里,runner 应该保持"声明式" - -### 与"纯 rename"的区别 - -| 维度 | 纯 rename(已有 skill 案例) | 多→1 冲突(本节案例) | -|---|---|---| -| 典型场景 | step6_5/6_75/8_5/8_75:1 个 requires 重命名到形参 | step5:2 个 requires 撞到同一形参 | -| parameter_map | 1 个 key→value | 2 个 key→同名 value + 占位名 | -| requires | 1 个字段 | 2 个字段(主 + 备) | -| 冲突来源 | 不会出现(单 key) | 出现(顺序敏感 + 撞名) | -| 修法 | 只加 parameter_map | 双向 parameter_map + 占位名 | - -## 与其他防御层的关系 - -``` -PipelineRunner.run() 主循环 - ├─ L1 runner.py:152 skip_when_missing ─── ctx. 全 None → skip step - ├─ L2 runner.py:182 ctx 字段注入 ─── 形参表里没声明 → TypeError ⚠️ → **kwargs 兜底 - ├─ L3 runner.py:188 user_config 合并 ─── user_config 有"空字符串"/None → 跳过(上一轮加的守卫)✅ - └─ L4 runner.py:211 except 捕获 ─── 业务抛异常 → ctx.status="error" + raise -``` - -`**kwargs` 是 **L2 的"消极兜底"**——宁愿吞掉多余 key 也不报 TypeError。**真正的"积极修复"是 parameter_map**(让 ctx 字段名映射到正确形参名)。两层配合: -- **保守期间(重构初期)**:先 `**kwargs` 兜住,TypeError 消失 -- **稳定阶段**:补 parameter_map,让方法收到正确数据 - -## 反例(不要做) - -- ❌ "不写 `**kwargs`,靠 type hint + IDE 检查兜底"——Runner 是运行时注入,IDE 看不到 -- ❌ "把 `**kwargs` 放形参表中间"——Python 语法错误 -- ❌ "改 requires 列表去掉冗余 ctx 字段"——会导致 `skip_when_missing` 误判(以为 step 不需要该 ctx 字段),应该用 `parameter_map` 重命名而非删除 requires -- ❌ "在 14 个 Facade 方法体里加 `if 'glint_mask_path' in kwargs: kwargs.pop('glint_mask_path')`"——脏活,且每个方法都要加,远不如 `**kwargs` 一行优雅 - -## 案例来源 - -- 2026-06-04 WQ_GUI PipelineRunner 迁移第二步 -- 触发:`step3_remove_glint() got an unexpected keyword argument 'glint_mask_path'` -- 根因:`PIPELINE_STEPS.step3.requires` 写了 `glint_mask_path`,但 `GlintRemovalStep` 内部使用,Facade 自身不接这个形参 -- 落地:14 个 Facade 全部加 `, **kwargs`,0 个 TypeError -- 验证:临时 `_check.py` 14/14 命中 + AST 解析通过 -- 续:4 个 parameter_map 全部落地(commit `64aa5b8`),含 step6_75 路由切到 indices_path;L3 非空过滤同步加入 `runner._invoke:188` -- 2026-06-04 step5 严格依赖修复:发现 L2 注入顺序冲突(requires 多个字段解析到同一形参名),引入"双向 parameter_map + 占位名落 **kwargs"模式;step5 形参 `csv_path` 真正接到 `processed_csv_path`(step4 产物),raw `csv_path` 保留为 user_config 覆盖入口,落占位名 `_raw_csv_ignored` 后被 `**kwargs` 吞。skip_when_missing 块同步加 `_notify` 通知,**拒绝静默跳过**(15 条 _notify 全带具体 missing 字段列表证据)。 diff --git a/.qwen/skills/wq_gui_data_flow/SKILL.md b/.qwen/skills/wq_gui_data_flow/SKILL.md deleted file mode 100644 index a3a4964..0000000 --- a/.qwen/skills/wq_gui_data_flow/SKILL.md +++ /dev/null @@ -1,206 +0,0 @@ ---- -name: WQ_GUI 数据流转架构 -description: WQ_GUI ProjectSession 事件总线驱动的步骤间数据传递机制(完整重构版) -source: auto-skill -extracted_at: '2026-05-28T09:07:34.967Z' ---- - -# WQ_GUI 数据流转架构 - -## 核心结论 - -整个系统是**基于文件路径驱动**的管道,所有数据存储在本地磁盘。重构后通过 `ProjectSession` 事件总线实现 Panel 间完全解耦。 - ---- - -## 1. 旧架构(旧代码中已删除) - -主窗口通过 `self.step_outputs` 字典 + `step_dependencies` 配置 + `auto_populate_*` 系列方法管理步骤间路径填充。存在高度耦合问题: - -```python -# 已废弃并删除 -self.step_outputs = {} -self._init_step_dependencies() -self.update_step_outputs(step_name, work_path) -self.auto_populate_dependent_steps(completed_step) -self.auto_populate_step_inputs(step_id) -self.find_step_output(work_path, step_id, output_type) -self.add_auto_fill_buttons_to_panels() -self.scan_work_directory_for_files(work_path) -``` - ---- - -## 2. 新架构:ProjectSession 事件总线 - -### Session 核心 API(`src/core/project_session.py`) - -```python -class ProjectSession(QObject): - path_updated = pyqtSignal(str, str, str) # step, out_type, path - step_outputs_ready = pyqtSignal(str, str) # step, out_type - - def update_output(step, out_type, path): - """Panel 完成后广播输出路径""" - - def update_outputs(step, {out_type: path, ...}): - """Panel 完成后批量广播多个输出路径""" - - def get_output(step, out_type): - """Panel 可主动查询上游路径(用于自动填充)""" - - def get_step_outputs(step): - """返回该 step 的全部输出字典""" - - def scan_work_directory(): - """主窗口 on_step_completed 末尾调用,扫描并广播所有已知路径""" -``` - -### Panel 重构模板 - -```python -class StepXPanel(QWidget): - def __init__(self, session=None, parent=None): - super().__init__(parent) - self.session = session - self.work_dir = None - self.init_ui() - self._bind_session_signals() - - def _bind_session_signals(self): - if not self.session: - return - self.session.path_updated.connect( - self._on_session_path_updated, Qt.QueuedConnection - ) - - @pyqtSlot(str, str, str) - def _on_session_path_updated(self, step_name, output_type, path): - print(f"[StepX Debug] 收到广播: step={step_name}, type={output_type}, path={path}") - if step_name == 'step1': - if output_type == 'reference_img': - if not self.img_file.get_path().strip(): - self.img_file.set_path(path) - print(f"[StepX] 自动填充参考影像: {path}") - elif output_type == 'water_mask': - if not self.water_mask_file.get_path().strip(): - self.water_mask_file.set_path(path) - print(f"[StepX] 自动填充水域掩膜: {path}") - # ... - - def on_step_finished(self, success, message): - """由主窗口 on_step_completed 通过 getattr 动态调用""" - if not success: - return - if self.session: - outputs = {} - path = self.output_widget.get_path().strip() - if path: - outputs['output_type'] = path - if outputs: - self.session.update_outputs('stepX', outputs) -``` - -### 主窗口两处改动 - -```python -# 1. __init__ 中注入 session(所有 Panel 统一注入) -self.step1_panel = Step1Panel(session=self.session) -self.step2_panel = Step2Panel(session=self.session) -self.step3_panel = Step3Panel(session=self.session) -self.step4_panel = Step4Panel(session=self.session) -self.step5_panel = Step5Panel(session=self.session) -self.step5_5_panel = Step5_5Panel(session=self.session) -self.step6_panel = Step6Panel(session=self.session) -self.step6_5_panel = Step6_5Panel(session=self.session) -self.step6_75_panel = Step6_75Panel(session=self.session) -self.step7_panel = Step7Panel(session=self.session) -self.step8_panel = Step8Panel(session=self.session) -self.step8_5_panel = Step8_5Panel(session=self.session) -self.step8_75_panel = Step8_75Panel(session=self.session) -self.step9_panel = Step9Panel(session=self.session) - -# 2. on_step_completed(通用动态获取,无需维护字典) -def on_step_completed(self, step_name, success, message): - if not success: - return - if hasattr(self, 'session') and self.session: - self.session.scan_work_directory() - - panel = getattr(self, f"{step_name}_panel", None) - if panel and hasattr(panel, 'on_step_finished'): - panel.on_step_finished(success, message) -``` - ---- - -## 3. 全链路事件流 - -### step1 → step2 / step3 路径(通过 Shapefile 栅格化产物) - -| 场景 | 广播的 water_mask 路径 | -|------|----------------------| -| NDWI 模式 | `output_file` 用户指定路径 | -| Shapefile 模式 | `{work_dir}/1_water_mask/water_mask_from_shp.dat`(优先)
若文件不存在则 fallback 回 `mask_file.get_path()` | - -``` -step1 完成 - → step1_panel.on_step_finished() - → session.update_outputs('step1', { - 'reference_img': img_path, - 'water_mask': mask_path # 可能是 .dat 或 .shp(见上表) - }) - → step2_panel._on_session_path_updated() - → step3_panel._on_session_path_updated() -``` - -### step3 → step5 / step7;step5 → 下游训练 - -``` -step3.deglint_image ──┬─→ step5.deglint_image(填充 img_file) - └─→ step7.deglint_image(填充 img_file) - -step5.training_spectra ──┬─→ step5_5.index_features - ├─→ step6.models_dir ──→ step8.predictions - ├─→ step6_5.models_dir ──→ step8_5.predictions - └─→ step6_75.models_dir ──→ step8_75.predictions - -step7.sampling_points ──┬─→ step8 - ├─→ step8_5 - └─→ step8_75 - -step8/8_5/8_75.predictions ──→ step9.distribution_map -``` - -### 各 Panel 监听/发布对照表(完整版) - -| Panel | 监听 | 发布 | -|-------|------|------| -| step1 | — | `reference_img`, `water_mask` | -| step2 | `step1.reference_img`, `step1.water_mask` | `glint_mask` | -| step3 | `step1.reference_img`, `step1.water_mask`, `step2.glint_mask` | `deglint_image` | -| step4 | — | `processed_data` | -| step5 | `step3.deglint_image`, `step4.processed_data`, `step2.glint_mask` | `training_spectra` | -| step5_5 | `step5.training_spectra` | `index_features` | -| step6 | `step5.training_spectra` | `models_dir` | -| step6_5 | `step5.training_spectra` | `models_dir` | -| step6_75 | `step5.training_spectra` | `models_dir` | -| step7 | `step3.deglint_image`, `step1.water_mask`, `step2.glint_mask` | `sampling_points` | -| step8 | `step7.sampling_points`, `step6.models_dir` | `predictions` | -| step8_5 | `step7.sampling_points`, `step6_5.models_dir` | `predictions` | -| step8_75 | `step7.sampling_points`, `step6_75.models_dir` | `predictions` | -| step9 | `step8.predictions`, `step8_5.predictions`, `step8_75.predictions` | `distribution_map` | - ---- - -## 4. 关键约束 - -- `__init__` 参数 `session=None`(向后兼容,主窗口可继续不传) -- 所有 Panel 的 `init_ui / get_config / set_config / update_from_config` 完整保留 -- 删除所有 `self.window().stepX_panel` 跨界访问 -- 使用 `self.session.get_output()` 替代直接读取其他 panel 的 widget -- 监听使用 `Qt.QueuedConnection` 确保跨线程安全 -- 仅在 field 为空时自动填充(`not widget.get_path().strip()`) -- `update_from_config` 中优先从 Session 获取路径,再用 Session 广播 -- 主窗口 `on_step_completed` 中使用 `getattr(self, f"{step_name}_panel", None)` 实现通用动态获取,无需维护硬编码字典 -- `step1` Shapefile 模式下,**不能**直接广播 `.shp` 输入文件,必须拼接 `{work_dir}/1_water_mask/water_mask_from_shp.dat` 作为产物路径 \ No newline at end of file diff --git a/.qwen/skills/wq_gui_external_model_panel/SKILL.md b/.qwen/skills/wq_gui_external_model_panel/SKILL.md deleted file mode 100644 index 4a53d35..0000000 --- a/.qwen/skills/wq_gui_external_model_panel/SKILL.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -name: WQ_GUI PyQt5 面板外部模型导入模式 -description: 在 Step8 等预测面板中通过 QRadioButton + FileSelectWidget + joblib.load 防御性加载实现"内置/导入"双模式切换的标准模式 -source: auto-skill -extracted_at: '2026-06-08T01:38:14.481Z' ---- - -# WQ_GUI PyQt5 面板外部模型导入模式 - -## 适用场景 - -Step8(机器学习预测)、Step8_5、Step8_75 等面板需要同时支持: -1. **内置模式**:使用 `step6` 训练流程生成的模型目录 -2. **导入模式**:用户手动选择本地预训练 `.joblib` 文件直接加载 - ---- - -## 1. 模板(可直接复制到 `__init__` + `init_ui`) - -```python -from PyQt5.QtWidgets import QRadioButton - -class StepXPanel(QWidget): - def __init__(self, parent=None): - super().__init__(parent) - self.current_model = None # ★ 外部模型实例缓存 - self.init_ui() - - def init_ui(self): - layout = QVBoxLayout() - - # -------- 模型来源选择(单选按钮组) -------- - source_group = QGroupBox("模型来源") - source_layout = QVBoxLayout() - - self.use_trained_model = QRadioButton("使用当前训练流程的模型") - self.use_external_model = QRadioButton("导入本地预训练模型 (.joblib)") - self.use_trained_model.setChecked(True) - source_layout.addWidget(self.use_trained_model) - source_layout.addWidget(self.use_external_model) - - self.use_trained_model.toggled.connect(self._on_model_source_changed) - self.use_external_model.toggled.connect(self._on_model_source_changed) - - source_group.setLayout(source_layout) - layout.addWidget(source_group) - - # -------- 外部模型文件选择(条件显示) -------- - self.external_model_widget = FileSelectWidget( - "预训练模型:", - "Joblib Files (*.joblib);;All Files (*.*)" - ) - # FileSelectWidget 的 browse_btn 默认连着 open file 行为, - # 需要先断开默认连接,再接自定义槽 - self.external_model_widget.browse_btn.clicked.disconnect() - self.external_model_widget.browse_btn.clicked.connect(self._browse_external_model) - self.external_model_widget.setVisible(False) - layout.addWidget(self.external_model_widget) - - # ... 其余原有 UI ... -``` - ---- - -## 2. 槽函数模板 - -### `_on_model_source_changed` - -单选按钮 `toggled` 信号在**两个**按钮上都会触发(点击 A 时 A 触发,B 也触发),所以用 `if not checked: return` 让非选中分支短路。 - -```python -def _on_model_source_changed(self, checked: bool): - """单选按钮切换:控制外部模型文件选择控件的显示/隐藏""" - if not checked: - return - is_external = self.use_external_model.isChecked() - self.external_model_widget.setVisible(is_external) - # 切回"使用当前模型"时清空缓存,释放内存并避免误用旧模型 - if not is_external: - self.current_model = None -``` - -### `_browse_external_model` - -- 用 `QFileDialog.getOpenFileName` 而非 `getExistingDirectory` -- 防御性解析两种格式:`{"model": pipeline, ...}`(Step6 输出格式)和裸 `Pipeline` 对象 -- 失败用 `QMessageBox.warning` 友善提示;成功用 `QMessageBox.information` 告知 - -```python -from PyQt5.QtWidgets import QFileDialog, QMessageBox -from pathlib import Path - -def _browse_external_model(self): - """浏览并加载外部 .joblib 预训练模型文件""" - default = self._get_default_work_dir() - path, _ = QFileDialog.getOpenFileName( - self, - "选择预训练模型 (.joblib)", - default, - "Joblib Files (*.joblib);;All Files (*.*)", - ) - if not path: - return - - try: - import joblib - loaded = joblib.load(path) - # 兼容两种格式:dict{"model": obj} 或裸 Pipeline - if isinstance(loaded, dict) and "model" in loaded: - self.current_model = loaded["model"] - elif hasattr(loaded, "predict"): - self.current_model = loaded - else: - QMessageBox.warning( - self, - "模型格式错误", - f"无法识别的模型格式,文件内容类型为:{type(loaded).__name__}", - ) - return - self.external_model_widget.set_path(path) - QMessageBox.information( - self, - "模型加载成功", - f"已加载模型:{Path(path).name}\n类型:{type(self.current_model).__name__}", - ) - except Exception as e: - self.current_model = None - QMessageBox.warning( - self, - "模型加载失败", - f"加载模型时发生错误:\n{type(e).__name__}: {e}", - ) -``` - ---- - -## 3. `run_step` 改造模板 - -在原有目录加载逻辑之前,插入外部模型优先分支: - -```python -def run_step(self): - """独立运行步骤X""" - # ... 公共输入校验 ... - - # ★ 外部模型优先分支 - if self.use_external_model.isChecked(): - if self.current_model is None: - QMessageBox.warning( - self, - "模型未加载", - "请先点击「浏览...」按钮加载预训练模型文件!", - ) - return - external_model_path = self.external_model_widget.get_path() or "" - main_window = self.window() - if hasattr(main_window, 'run_single_step'): - config = { - 'stepX': self.get_config(), - '_external_model': self.current_model, # ★ 直接传对象 - '_external_model_path': external_model_path, # 供日志/回溯用 - } - main_window.run_single_step('stepX', config) - return - - # 默认流程:使用模型目录(原有逻辑不变) - models_dir = self.models_dir_file.get_path() - if not models_dir: - QMessageBox.warning(self, "输入错误", "请选择模型目录!") - return - # ... 原有 run_step 剩余代码 ... -``` - ---- - -## 4. 后端三层完整接入(2026-06-08 已落地) - -完整数据流分为三层,每层各一处分流点: - -``` -GUI step8_panel - ↓ config = {'_external_model': obj, '_external_model_path': path, 'step8': {...}} - ↓ -worker_thread.run_single_step() [第1处分流:透传顶层 key] - ↓ step_config = config['step8'] + {'_external_model': obj, '_external_model_path': path} - ↓ -prediction_step.predict_water_quality() [第2处分流:接收 + 透传] - ↓ _external_model=obj, _external_model_path=path - ↓ -WaterQualityInference(artifacts_dir, external_model=obj, external_model_path=path) - ↓ -inference_batch.batch_inference_multi_models() [第3处分流:effective_model 短路] - ↓ external_model=obj - ↓ -inference_batch.inference_pipeline() - → self.external_model is not None → self.loaded_model_data = self.external_model(跳过磁盘加载) -``` - -### 4a. worker_thread.py — run_single_step 透传 - -在 `step_config = dict(config.get(step_name, {}))` 之后、"skip_dependency_check" 之前插入: - -```python -# 透传面板顶层传入的外部预训练模型(GUI step8_panel 通过 config['_external_model'] 传入) -# 非空才覆盖(遵循 feedback_never_overwrite_with_empty 原则) -for key in ('_external_model', '_external_model_path'): - val = config.get(key) - if val is not None and val != "": - step_config[key] = val -``` - -### 4b. prediction_step.py — predict_water_quality 签名 + 透传 - -形参表末尾增加两个参数: - -```python -_external_model=None, -_external_model_path=None, -``` - -构造处透传: - -```python -inferencer = WaterQualityInference( - models_dir, - external_model=_external_model, - external_model_path=_external_model_path, -) -all_results = inferencer.batch_inference_multi_models( - models_root_dir=models_dir, - ... - external_model=_external_model, - external_model_path=_external_model_path, -) -``` - -### 4c. inference_batch.py — 三处修改 - -**① `__init__` 存储**: - -```python -def __init__(self, artifacts_dir: str = "models/artifacts", - external_model=None, external_model_path=None): - ... - self.external_model = external_model - self.external_model_path = external_model_path -``` - -**② `batch_inference_multi_models` 短路 + 注入**: - -```python -# 优先级:外部预训练模型 > 从磁盘加载 -if external_model is not None: - effective_model = external_model - print(f"\n使用外部预训练模型: type={type(external_model).__name__}") -else: - effective_model = None - -# 子目录循环中注入: -if effective_model is not None: - model_inferencer = WaterQualityInference( - str(subdir), - external_model=effective_model, - external_model_path=external_model_path, - ) -else: - model_inferencer = WaterQualityInference(str(subdir)) -``` - -**③ `inference_pipeline` 模型加载短路**(`load_best_model` 调用前): - -```python -if self.external_model is not None: - self.loaded_model_data = self.external_model - print(f" 使用外部预训练模型: type={type(self.external_model).__name__}") -elif model_file_path: - self.load_specific_model(model_file_path) -else: - self.load_best_model(metric=metric) -``` - -**关键约束**: -- `joblib.load` 在 panel 槽函数里完成(GUI 进程内),对象通过 config 引用直接透传;**不跨进程**,所以不需要担心 pickle 序列化问题 -- `batch_inference_multi_models` 形参 `external_model` 和 `external_model_path` **与实例属性同名**(`self.external_model`),两者都传是为了让每个子目录创建的 `WaterQualityInference` 实例都能独立持有引用 -- 原有从 `models_dir` 目录加载的逻辑完全保留,只在 `external_model is not None` 时短路 - ---- - -## 5. 已知约束 - -- `FileSelectWidget.browse_btn.clicked` 在 `init_ui` 里会重复 connect,每次 `init_ui` 被调用时会累积;解决方案是在 connect 前先 `.disconnect()`(如模板所示)。 -- `QRadioButton.toggled` 信号在两个按钮上都会触发,**必须**用 `if not checked: return` 短路,否则会导致切换时状态错乱。 -- `self.current_model` 会在面板切换到"使用当前模型"时清空,防止用户忘记换回内置模式后仍使用旧导入模型。 -- 当前项目 venv 路径:`D:\111\office\ZHLduijie\1.WQ\WQ_GUI\venv`,导入 `joblib` 时注意 venv 环境一致性。 \ No newline at end of file diff --git a/.qwen/skills/wq_gui_frontend_scaffold/SKILL.md b/.qwen/skills/wq_gui_frontend_scaffold/SKILL.md deleted file mode 100644 index 5d26c65..0000000 --- a/.qwen/skills/wq_gui_frontend_scaffold/SKILL.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -name: WQ_GUI 前端 Vue3 + Element Plus 脚手架 -description: WQ_GUI 项目 frontend/ 目录的 Vite + Vue 3 + TS + Element Plus 最小可运行脚手架,以及 useTaskPoller 与 Element Plus UI 的接线模式 -source: auto-skill -extracted_at: '2026-06-02T08:17:33.116Z' ---- - -# WQ_GUI 前端脚手架 (Vue 3 + Element Plus) - -## 适用场景 - -为 WQ_GUI FastAPI 后端 (`127.0.0.1:8000`) 搭建一个**最小可联调**的浏览器控制台。 -后端已暴露: - -- `POST /api/modeling/train` → `{ task_id, status, kind }` -- `POST /api/modeling/predict` → `{ task_id, status, kind }` -- `GET /api/tasks/{task_id}` → `TaskRecord`(含 PENDING/PROCESSING/SUCCESS/FAILED + 模型指标 / 输出路径) -- `GET /api/algorithms` → 算法清单 - -前端已有 (`frontend/src/`): - -- `api/request.ts`:axios 单例 + 响应拦截器自动 unwrap,baseURL 走 `VITE_API_BASE_URL` 缺省 `http://127.0.0.1:8000` -- `api/tasks.ts`:所有提交 / 查询函数 + 完整 `TaskRecord` / `TaskStatus` / `TaskKind` 类型 -- `composables/useTaskPoller.ts`:完整轮询 composable,支持 3 种用法(静态 / 响应式 taskId / 手动) - -## 1. 一次性补齐的脚手架文件 - -`frontend/` 初始状态**只有 `src/api` 和 `src/composables`**,缺整个 Vite 骨架。直接照下面这 7 个文件铺一遍: - -``` -frontend/ -├── .env.development # VITE_API_BASE_URL=http://127.0.0.1:8000 -├── .gitignore # node_modules / dist / .vite -├── env.d.ts # vite/client + ImportMeta + *.vue shim -├── index.html # 挂载 #app -├── package.json -├── tsconfig.json # 严格模式 + @ → src + bundler resolution -├── tsconfig.node.json # 给 vite.config.ts 用 -├── vite.config.ts # @ alias + 0.0.0.0:5173 -└── src/ - ├── main.ts - └── App.vue -``` - -### 锁定版本(2026-06 联调通过) - -```json -{ - "dependencies": { - "vue": "^3.4.27", - "element-plus": "^2.7.5", - "@element-plus/icons-vue": "^2.3.1", - "axios": "^1.7.2" - }, - "devDependencies": { - "@types/node": "^20.12.12", - "@vitejs/plugin-vue": "^5.0.4", - "typescript": "^5.4.5", - "vite": "^5.2.11", - "vue-tsc": "^2.0.19" - } -} -``` - -**`@types/node` 必加**——`vite.config.ts` 用了 `import { fileURLToPath, URL } from 'node:url'`,否则 `npm run build` 类型检查必挂。 - -### `tsconfig.json` 关键字段 - -- `"moduleResolution": "bundler"` -- `"allowImportingTsExtensions": true`(配合 `vue-tsc --noEmit`) -- `"paths": { "@/*": ["src/*"] }` + `"baseUrl": "."` -- `"include": ["src/**/*.vue"]`(`vue-tsc` 才会处理 SFC) -- `"references": [{ "path": "./tsconfig.node.json" }]` - -### `vite.config.ts` 关键字段 - -```ts -resolve: { - alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }, -}, -server: { host: '0.0.0.0', port: 5173 }, -``` - -`0.0.0.0` 方便局域网真机调试;端口冲突时 `strictPort: false` 允许 Vite 自动 +1。 - ---- - -## 2. main.ts 模板(全量注册 Element Plus) - -```ts -import { createApp } from 'vue' -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' -import * as ElementPlusIconsVue from '@element-plus/icons-vue' - -import App from './App.vue' - -const app = createApp(App) -app.use(ElementPlus) - -// 全量注册图标 () -for (const [name, component] of Object.entries(ElementPlusIconsVue)) { - app.component(name, component) -} -app.mount('#app') -``` - -联调期**全量注册最省事**;后期打包体积大再换 `unplugin-vue-components` 按需。 - ---- - -## 3. useTaskPoller 接线模式(双实例) - -训练 / 推断是**两条独立流水线**,各起一个 `useTaskPoller` 实例。核心套路:把 `task_id` 包成 `ref(null)`,composable 内部 `watch` 会**自动 start()**,无需手动调: - -```ts -import { ref, watch, computed } from 'vue' -import { submitTrain, submitPredict, type TaskRecord } from './api/tasks' -import { useTaskPoller } from './composables/useTaskPoller' - -// —— 训练 —— -const trainTaskId = ref(null) -const trainPoller = useTaskPoller(trainTaskId) // 传 ref 进去, 自动 watch - -async function onStartTrain() { - const { task_id } = await submitTrain({ ... }) - trainTaskId.value = task_id // 赋值后 watch 触发 start() -} - -// —— 推断 —— -const predictTaskId = ref(null) -const predictPoller = useTaskPoller(predictTaskId) -const modelId = ref('') - -// 训练一成功, model_id 自动填入推断输入框 -watch( - () => trainPoller.result.value?.model_id, - (newId) => { if (newId) modelId.value = newId }, -) - -async function onStartPredict() { - const { task_id } = await submitPredict({ model_id: modelId.value, ... }) - predictTaskId.value = task_id -} -``` - -**关键点**: - -- `trainPoller.result.value` 才是 SUCCESS 后的完整 `TaskRecord`;`record.value` 是任意时刻(含中间态)的最新记录。模板里同时展示用 `trainPoller.record.value ?? trainPoller.result.value`。 -- `poller.isPolling.value` / `poller.status.value` / `poller.error.value` / `poller.taskId.value` 都是 `Ref`,模板里必须用 `.value`(它们是嵌套 ref,**Vue 模板不会自动 unwrap**)。 - ---- - -## 4. el-progress 状态映射 - -`PollerStatus = 'idle' | 'PENDING' | 'PROCESSING' | 'SUCCESS' | 'FAILED'` -`el-progress` 的 `status` 接受 `'' | 'success' | 'warning' | 'exception'`。 - -```ts -function progressOf(status: string): number { - switch (status) { - case 'idle': - case 'PENDING': return 10 - case 'PROCESSING':return 60 - case 'SUCCESS': - case 'FAILED': return 100 - default: return 0 - } -} -function progressStatusOf(s: string): '' | 'success' | 'exception' { - if (s === 'SUCCESS') return 'success' - if (s === 'FAILED') return 'exception' - return '' -} -``` - -模板里 `v-if="poller.isPolling.value || poller.status.value === 'SUCCESS' || poller.status.value === 'FAILED'"` 控制展示。 - ---- - -## 5. CSS:深色控制台风(slate 渐变 + 卡片玻璃态) - -```css -.app-root { - min-height: 100vh; - background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%); - color: #e2e8f0; -} -.panel { - background: rgba(30, 41, 59, 0.7) !important; - border: 1px solid rgba(148, 163, 184, 0.18) !important; -} -.app-main { - display: grid; - grid-template-columns: 1fr 1fr; /* 左训练 / 右推断 */ - gap: 20px; -} -@media (max-width: 960px) { .app-main { grid-template-columns: 1fr; } } -``` - -深色背景下 Element Plus 的 `el-form-item__label` / `el-descriptions__label` 默认是黑色文字,必须 `:deep()` 覆盖成浅色。 - ---- - -## 6. 启动与验证 - -```bat -cd /d D:\111\office\ZHLduijie\1.WQ\WQ_GUI\frontend -npm install -npm run dev -``` - -打开 `http://127.0.0.1:5173/`,联调期望路径: - -1. 左侧「开始训练」→ 立即拿到 `task_id` + 黄色 `轮询中` + 进度条 60% -2. 后端 SUCCESS → 进度条变绿,下面出现 `model_id` 标签 + R²/RMSE/MAE -3. 右侧 `model_id` 被自动填入 → 「开始推断」→ 走 `output_zarr_path` 展示 -4. 任何一步 FAILED → 进度条变红 + 后端 `error` 字段 - ---- - -## 7. 已知 caveat - -- **第一次 `npm install` 约 150MB**,要耐心等。 -- `useTaskPoller` 已有 `onUnmounted` 自动清理,**不要再手写 `clearInterval`**。 -- `request.ts` 注释里写明 FastAPI dev 期 `allow_origins=["*"]`,**不需要配 Vite proxy**;如果未来后端收紧 CORS,再在 `vite.config.ts` 加 `server.proxy['/api']`。 -- `feature_start` 后端接受 `number | string`;el-input v-model 出来是 string,**直接传给 API 即可**,后端会自己判别。 -- `v-model` 绑 `ref(4)` 类型注解是必须的,否则 TS 会推断成 `Ref`,输入框失焦报错。 -- `@element-plus/icons-vue` 全量注册后用 `` 调,本期 App.vue 没用到但留着扩展位。 diff --git a/1.py b/1.py deleted file mode 100644 index e6c9144..0000000 --- a/1.py +++ /dev/null @@ -1,4 +0,0 @@ - - -new_wavelengths = [np.mean(wavelengths[i:i+3]) for i in range(0, len(wavelengths), 3)] -print(new_wavelengths) \ No newline at end of file diff --git a/data/sub/png/watermask.png b/data/sub/png/watermask.png deleted file mode 100644 index 860cd4c..0000000 Binary files a/data/sub/png/watermask.png and /dev/null differ diff --git a/data/sub/waterindex.csv b/data/sub/waterindex.csv deleted file mode 100644 index 4a04128..0000000 --- a/data/sub/waterindex.csv +++ /dev/null @@ -1,46 +0,0 @@ -Formula_Name,Category,Formula,Reference -BGA_Am09KBBI,Phycocyanin (BGA_PC),(w686 - w658) / (w686 + w658),"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S.; Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery, Optics Express, 2009, 17, 11, 1-13." -BGA_Be162B643sub629,Phycocyanin (BGA_PC),w644 - w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538." -BGA_Be162B700sub601,Phycocyanin (BGA_PC),w700 - w601,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539." -BGA_Be162BsubPhy,Phycocyanin (BGA_PC),w715 - w615,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540." -BGA_Be16FLHBlueRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w458 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538." -BGA_Be16FLHGreenRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w558 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539." -BGA_Be16FLHVioletRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w444 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538." -BGA_Be16MPI,Phycocyanin (BGA_PC),(w615 - w601) - (w644 - w601),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539." -BGA_Be16NDPhyI,Phycocyanin (BGA_PC),(w700 - w622) / (w700 + w622),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540." -BGA_Be16NDPhyI644over615,Phycocyanin (BGA_PC),(w644 - w615) / (w644 + w615),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 541." -BGA_Be16NDPhyI644over629,Phycocyanin (BGA_PC),(w644 - w629) / (w644 + w629),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 542." -BGA_Be16Phy2BDA644over629,Phycocyanin (BGA_PC),w644 / w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 545." -BGA_Da052BDA,Phycocyanin (BGA_PC),w714 / w672,"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672." -BGA_Go04MCI,Phycocyanin (BGA_PC),w709 - w681 - (w753 - w681),"Gower, J.F.R.; Brown,L.; Borstad, G.A.; Observation of chlorophyll fluorescence in west coast waters of Canada using the MODIS satellite sensor. Can. J. Remote Sens., 2004, 30 (1), 17?5." -BGA_HU103BDA,Phycocyanin (BGA_PC),(((1 / w615) - (1 / w600)) - w725),"Hunter, P.D.; Tyler, A.N.; Willby, N.J.; Gilvear, D.J.; The spatial dynamics of vertical migration by Microcystis aeruginosa in a eutrophic shallow lake: A case study using high spatial resolution time-series airborne remote sensing. Limn. Oceanogr. 2008, 53, 2391-2406" -BGA_Ku15PhyCI,Phycocyanin (BGA_PC),(-1 * (W681 - W665 - (W709 - W665))),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-10." -BGA_Ku15SLH,Phycocyanin (BGA_PC),(w715 - w658) + (w715 - w658),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-11." -BGA_MI092BDA,Phycocyanin (BGA_PC),w700 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758?75." -BGA_MM092BDA,Phycocyanin (BGA_PC),w724 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758?76." -BGA_MM12NDCIalt,Phycocyanin (BGA_PC),(w700 - w658) / (w700 + w658),"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114003" -BGA_MM143BDAopt,Phycocyanin (BGA_PC),((1 / w629) - (1 / w659)) * w724,"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114004" -BGA_SI052BDA,Phycocyanin (BGA_PC),w709 / w620,"Simis, S. G. H.; Peters, S.W. M.; Gons, H. J.; Remote sensing of the cyanobacteria pigment phycocyanin in turbid inland water. Limn. Oceanogr., 2005, 50, 237?45" -BGA_SM122BDA,Phycocyanin (BGA_PC),w709 / w600,"Mishra, S. Remote sensing of cyanobacteria in turbid productive waters, PhD Dissertation. Mississippi State University, USA. 2012." -BGA_SY002BDA,Phycocyanin (BGA_PC),w650 / w625,"Schalles, J.; Yacobi, Y. Remote detection and seasonal patterns of phycocyanin, carotenoid and chlorophyll-a pigments in eutrophic waters. Archiv fur Hydrobiologie, Special Issues Advances in Limnology, 2000, 55,153?68" -BGA_Wy08CI,Phycocyanin (BGA_PC),(-1 * (W686 - W672 - (W715 - W672))),"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672." -Chl_Al10SABI,chlorophyll_a,(w857 - w644) / (w458 + w529),"Alawadi, F. Detection of surface algal blooms using the newly developed algorithm surface algal bloom index (SABI). Proc. SPIE 2010, 7825." -Chl_Am092Bsub,chlorophyll_a,w681 - w665,"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S. Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery. Opt. Express 2009, 17, 9126?144." -Chl_Be16FLHblue,chlorophyll_a,w529 - (w644 + (w458 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30." -Chl_Be16FLHviolet,chlorophyll_a,w529 - (w644 + (w429 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30." -Chl_Be16NDTIblue,chlorophyll_a,(w658 - w458) / (w658 + w458),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 543." -Chl_Be16NDTIviolet,chlorophyll_a,(w658 - w444) / (w658 + w444),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 544." -Chl_De933BDA,chlorophyll_a,w600 - w648 - w625,"Dekker, A.; Detection of the optical water quality parameters for eutrophic waters by high resolution remote sensing, Ph.D. thesis, 1993, Free University, Amsterdam." -Chl_Gi033BDA,chlorophyll_a,((1 / w672) - (1 / w715)) * w757,"Gitelson, A.A.; U. Gritz, and M. N. Merzlyak.; Relationships between leaf chlorophyll content and spectral reflectance and algorithms for non-destructive chlorophyll assessment in higher plant leaves. J. Plant Phys. 2003, 160, 271-282." -Chl_Kn07KIVU,chlorophyll_a,(w458 - w644) / w529,"Kneubuhler, M.; Frank T.; Kellenberger, T.W; Pasche N.; Schmid M.; Mapping chlorophyll-a in Lake Kivu with remote sensing methods. 2007, Proceedings of the Envisat Symposium 2007, Montreux, Switzerland 23?7 April 2007 (ESA SP-636, July 2007)." -Chl_MM12NDCI,chlorophyll_a,(w715 - w686) / (w715 + w686),"Mishra, S.; and Mishra, D.R. Normalized difference chlorophyll index: A novel model for remote estimation of chlorophyll-a concentration in turbid productive waters, Remote Sens. Environ., 2012, 117, 394-406" -Chl_Zh10FLH,chlorophyll_a,w686 - (w715 + (w672 - w751)),"Zhao, D.Z.; Xing, X.G.; Liu, Y.G.; Yang, J.H.; Wang, L. The relation of chlorophyll-a concentration with the reflectance peak near 700 nm in algae-dominated waters and sensitivity of fluorescence algorithms for detecting algal bloom. Int. J. Remote Sens. 2010, 31, 39-48" -Turb_Be16GreenPlusRedBothOverViolet,Turbidity,(w558 + w658) / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538" -Turb_Be16RedOverViolet,Turbidity,w658 / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539" -Turb_Bow06RedOverGreen,Turbidity,w658 / w558,"Bowers, D. G., and C. E. Binding. 2006. 闁炽儲缈籬e Optical Properties of Mineral Suspended Particles: A Review and Synthesis.?Estuarine Coastal and Shelf Science 67 (1?): 219?30. doi:10.1016/j.ecss.2005.11.010" -Turb_Chip09NIROverGreen,Turbidity,w857 / w558,"Chipman, J. W.; Olmanson, L.G.; Gitelson, A.A.; Remote sensing methods for lake management: A guide for resource managers and decision-makers. 2009." -Turb_Dox02NIRoverRed,Turbidity,w857 / w658,"Doxaran, D., Froidefond, J.-M.; Castaing, P. ; A reflectance band ratio used to estimate suspended matter concentrations in sediment-dominated coastal waters, Remote Sens., 2002, 23, 5079-5085" -Turb_Frohn09GreenPlusRedBothOverBlue,Turbidity,(w558 + w658) / w458,"Frohn, R. C., & Autrey, B. C. (2009). Water quality assessment in the Ohio River using new indices for turbidity and chlorophyll-a with Landsat-7 Imagery. Draft Internal Report, US Environmental Protection Agency." -Turb_Harr92NIR,Turbidity,w857,"Schiebe F.R., Harrington J.A., Ritchie J.C. Remote-Sensing of Suspended Sediments闁炽儲鏁刪e Lake Chicot, Arkansas Project. Int. J. Remote Sens. 1992;13:1487?509" -Turb_Lath91RedOverBlue,Turbidity,w658 / w458,"Lathrop, R. G., Jr., T. M. Lillesand, and B. S. Yandell, 1991. Testing the utility of simple multi-date Thematic Mapper calibration algorithms for monitoring turbid inland waters. International Journal of Remote Sensing" -Turb_Moore80Red,Turbidity,w658,"Moore, G.K., Satellite remote sensing of water turbidity, Hydrological Sciences, 1980, 25, 4, 407-422" diff --git a/data/sub/waterindex.xlsx b/data/sub/waterindex.xlsx deleted file mode 100644 index bf1d6e2..0000000 Binary files a/data/sub/waterindex.xlsx and /dev/null differ diff --git a/data/sub/waterindex1125.csv b/data/sub/waterindex1125.csv deleted file mode 100644 index 7fa5dc4..0000000 --- a/data/sub/waterindex1125.csv +++ /dev/null @@ -1,46 +0,0 @@ -Formula_Name,Category,Formula,Reference -BGA_Am09KBBI,Phycocyanin (BGA_PC),(w686 - w658) / (w686 + w658),"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S.; Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery, Optics Express, 2009, 17, 11, 1-13." -BGA_Be162B643sub629,Phycocyanin (BGA_PC),w644 - w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538." -BGA_Be162B700sub601,Phycocyanin (BGA_PC),w700 - w601,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539." -BGA_Be162BsubPhy,Phycocyanin (BGA_PC),w715 - w615,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540." -BGA_Be16FLHBlueRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w458 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538." -BGA_Be16FLHGreenRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w558 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539." -BGA_Be16FLHVioletRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w444 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538." -BGA_Be16MPI,Phycocyanin (BGA_PC),(w615 - w601) - (w644 - w601),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539." -BGA_Be16NDPhyI,Phycocyanin (BGA_PC),(w700 - w622) / (w700 + w622),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540." -BGA_Be16NDPhyI644over615,Phycocyanin (BGA_PC),(w644 - w615) / (w644 + w615),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 541." -BGA_Be16NDPhyI644over629,Phycocyanin (BGA_PC),(w644 - w629) / (w644 + w629),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 542." -BGA_Be16Phy2BDA644over629,Phycocyanin (BGA_PC),w644 / w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 545." -BGA_Da052BDA,Phycocyanin (BGA_PC),w714 / w672,"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672." -BGA_Go04MCI,Phycocyanin (BGA_PC),w709 - w681 - (w753 - w681),"Gower, J.F.R.; Brown,L.; Borstad, G.A.; Observation of chlorophyll fluorescence in west coast waters of Canada using the MODIS satellite sensor. Can. J. Remote Sens., 2004, 30 (1), 17?5." -BGA_HU103BDA,Phycocyanin (BGA_PC),(((1 / w615) - (1 / w600)) - w725),"Hunter, P.D.; Tyler, A.N.; Willby, N.J.; Gilvear, D.J.; The spatial dynamics of vertical migration by Microcystis aeruginosa in a eutrophic shallow lake: A case study using high spatial resolution time-series airborne remote sensing. Limn. Oceanogr. 2008, 53, 2391-2406" -BGA_Ku15PhyCI,Phycocyanin (BGA_PC),-1 * (W681 - W665 - (W709 - W665)),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-10." -BGA_Ku15SLH,Phycocyanin (BGA_PC),(w715 - w658) + (w715 - w658),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-11." -BGA_MI092BDA,Phycocyanin (BGA_PC),w700 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758?75." -BGA_MM092BDA,Phycocyanin (BGA_PC),w724 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758?76." -BGA_MM12NDCIalt,Phycocyanin (BGA_PC),(w700 - w658) / (w700 + w658),"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114003" -BGA_MM143BDAopt,Phycocyanin (BGA_PC),((1 / w629) - (1 / w659)) * w724,"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114004" -BGA_SI052BDA,Phycocyanin (BGA_PC),w709 / w620,"Simis, S. G. H.; Peters, S.W. M.; Gons, H. J.; Remote sensing of the cyanobacteria pigment phycocyanin in turbid inland water. Limn. Oceanogr., 2005, 50, 237?45" -BGA_SM122BDA,Phycocyanin (BGA_PC),w709 / w600,"Mishra, S. Remote sensing of cyanobacteria in turbid productive waters, PhD Dissertation. Mississippi State University, USA. 2012." -BGA_SY002BDA,Phycocyanin (BGA_PC),w650 / w625,"Schalles, J.; Yacobi, Y. Remote detection and seasonal patterns of phycocyanin, carotenoid and chlorophyll-a pigments in eutrophic waters. Archiv fur Hydrobiologie, Special Issues Advances in Limnology, 2000, 55,153?68" -BGA_Wy08CI,Phycocyanin (BGA_PC),-1 * (W686 - W672 - (W715 - W672)),"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672." -Chl_Al10SABI,chlorophyll_a,(w857 - w644) / (w458 + w529),"Alawadi, F. Detection of surface algal blooms using the newly developed algorithm surface algal bloom index (SABI). Proc. SPIE 2010, 7825." -Chl_Am092Bsub,chlorophyll_a,w681 - w665,"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S. Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery. Opt. Express 2009, 17, 9126?144." -Chl_Be16FLHblue,chlorophyll_a,w529 - (w644 + (w458 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30." -Chl_Be16FLHviolet,chlorophyll_a,w529 - (w644 + (w429 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30." -Chl_Be16NDTIblue,chlorophyll_a,(w658 - w458) / (w658 + w458),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 543." -Chl_Be16NDTIviolet,chlorophyll_a,(w658 - w444) / (w658 + w444),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 544." -Chl_De933BDA,chlorophyll_a,w600 - w648 - w625,"Dekker, A.; Detection of the optical water quality parameters for eutrophic waters by high resolution remote sensing, Ph.D. thesis, 1993, Free University, Amsterdam." -Chl_Gi033BDA,chlorophyll_a,((1 / w672) - (1 / w715)) * w757,"Gitelson, A.A.; U. Gritz, and M. N. Merzlyak.; Relationships between leaf chlorophyll content and spectral reflectance and algorithms for non-destructive chlorophyll assessment in higher plant leaves. J. Plant Phys. 2003, 160, 271-282." -Chl_Kn07KIVU,chlorophyll_a,(w458 - w644) / w529,"Kneubuhler, M.; Frank T.; Kellenberger, T.W; Pasche N.; Schmid M.; Mapping chlorophyll-a in Lake Kivu with remote sensing methods. 2007, Proceedings of the Envisat Symposium 2007, Montreux, Switzerland 23?7 April 2007 (ESA SP-636, July 2007)." -Chl_MM12NDCI,chlorophyll_a,(w715 - w686) / (w715 + w686),"Mishra, S.; and Mishra, D.R. Normalized difference chlorophyll index: A novel model for remote estimation of chlorophyll-a concentration in turbid productive waters, Remote Sens. Environ., 2012, 117, 394-406" -Chl_Zh10FLH,chlorophyll_a,w686 - (w715 + (w672 - w751)),"Zhao, D.Z.; Xing, X.G.; Liu, Y.G.; Yang, J.H.; Wang, L. The relation of chlorophyll-a concentration with the reflectance peak near 700 nm in algae-dominated waters and sensitivity of fluorescence algorithms for detecting algal bloom. Int. J. Remote Sens. 2010, 31, 39-48" -Turb_Be16GreenPlusRedBothOverViolet,Turbidity,(w558 + w658) / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538" -Turb_Be16RedOverViolet,Turbidity,w658 / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539" -Turb_Bow06RedOverGreen,Turbidity,w658 / w558,"Bowers, D. G., and C. E. Binding. 2006. 鈥淭he Optical Properties of Mineral Suspended Particles: A Review and Synthesis.?Estuarine Coastal and Shelf Science 67 (1?): 219?30. doi:10.1016/j.ecss.2005.11.010" -Turb_Chip09NIROverGreen,Turbidity,w857 / w558,"Chipman, J. W.; Olmanson, L.G.; Gitelson, A.A.; Remote sensing methods for lake management: A guide for resource managers and decision-makers. 2009." -Turb_Dox02NIRoverRed,Turbidity,w857 / w658,"Doxaran, D., Froidefond, J.-M.; Castaing, P. ; A reflectance band ratio used to estimate suspended matter concentrations in sediment-dominated coastal waters, Remote Sens., 2002, 23, 5079-5085" -Turb_Frohn09GreenPlusRedBothOverBlue,Turbidity,(w558 + w658) / w458,"Frohn, R. C., & Autrey, B. C. (2009). Water quality assessment in the Ohio River using new indices for turbidity and chlorophyll-a with Landsat-7 Imagery. Draft Internal Report, US Environmental Protection Agency." -Turb_Harr92NIR,Turbidity,w857,"Schiebe F.R., Harrington J.A., Ritchie J.C. Remote-Sensing of Suspended Sediments鈥攖he Lake Chicot, Arkansas Project. Int. J. Remote Sens. 1992;13:1487?509" -Turb_Lath91RedOverBlue,Turbidity,w658 / w458,"Lathrop, R. G., Jr., T. M. Lillesand, and B. S. Yandell, 1991. Testing the utility of simple multi-date Thematic Mapper calibration algorithms for monitoring turbid inland waters. International Journal of Remote Sensing" -Turb_Moore80Red,Turbidity,w658,"Moore, G.K., Satellite remote sensing of water turbidity, Hydrological Sciences, 1980, 25, 4, 407-422" diff --git a/new/app/api/_smoke_test_train.py b/new/app/api/_smoke_test_train.py deleted file mode 100644 index 63404b0..0000000 --- a/new/app/api/_smoke_test_train.py +++ /dev/null @@ -1,201 +0,0 @@ -""" -冒烟测试 _run_train_sync: 用合成数据走通真实训练管线。 -不依赖 FastAPI / xarray / dask, 只验训练 + 持久化 + 回测。 -""" -import sys -import tempfile -from pathlib import Path - -import numpy as np -import pandas as pd - -# 绕过 main.py 触发 app 包导入(只导入 modeling 模块) -# 当前文件位于 new/app/api/_smoke_test_train.py -# app 包在 new/app/__init__.py, 故 new/ 必须在 sys.path 上 -sys.path.insert(0, str(Path(__file__).parent.parent.parent)) - -from app.api.modeling import ( - _get_model_pipeline, - _load_train_df, - _resolve_feature_start, - _run_train_sync, - _MODEL_CLASS_REGISTRY, -) - - -def make_synthetic_csv(n_samples: int = 200, n_features: int = 8, noise: float = 0.1, seed: int = 42) -> Path: - """生成 [lat, lon, target, lat2, lon2, feat_0, feat_1, ...] 布局的 CSV""" - rng = np.random.default_rng(seed) - lat = rng.uniform(20, 25, n_samples) - lon = rng.uniform(110, 115, n_samples) - target = rng.uniform(0, 50, n_samples) - lat2 = rng.uniform(0, 1, n_samples) # 元数据 - lon2 = rng.uniform(0, 1, n_samples) # 元数据 - feats = rng.normal(0, 1, (n_samples, n_features)) - # 让 y 真正依赖前 3 个特征, RF 至少应该能学到 R² > 0.5 - feats[:, 0] += target / 10 - feats[:, 1] += target / 20 - feats[:, 2] -= target / 15 - - df = pd.DataFrame({ - "lat": lat, - "lon": lon, - "Chl-a": target, - "lat2": lat2, - "lon2": lon2, - **{f"feat_{i}": feats[:, i] for i in range(n_features)}, - }) - tmp = Path(tempfile.mkdtemp()) / "train.csv" - df.to_csv(tmp, index=False) - return tmp - - -def test_load_train_df(): - print("== test_load_train_df ==") - p = make_synthetic_csv(n_samples=50) - df = _load_train_df(str(p)) - assert df.shape == (50, 5 + 8), f"shape={df.shape}" - print(f" shape={df.shape}, columns[:6]={list(df.columns[:6])}") - print(" PASS") - - -def test_resolve_feature_start_int_and_str(): - print("== test_resolve_feature_start (int + str) ==") - p = make_synthetic_csv() - df = _load_train_df(str(p)) - idx_int = _resolve_feature_start(df, 5) - idx_str = _resolve_feature_start(df, "feat_0") - assert idx_int == 5 == idx_str, f"int={idx_int}, str={idx_str}" - print(f" int(5) -> {idx_int}, str('feat_0') -> {idx_str}") - print(" PASS") - - -def test_resolve_feature_start_str_miss(): - print("== test_resolve_feature_start (str 不存在 -> 抛错) ==") - p = make_synthetic_csv() - df = _load_train_df(str(p)) - try: - _resolve_feature_start(df, "not_exist") - print(" FAIL: 应抛 ValueError") - except ValueError as e: - print(f" 正确抛 ValueError: {e}") - print(" PASS") - - -def test_get_model_pipeline_all_types(): - print("== test_get_model_pipeline (5 种 model_type) ==") - for mt in ["RF", "SVR", "LinearRegression", "KNN", "PLS"]: - p = _get_model_pipeline(mt, {}) - assert len(p.steps) == 2 - assert p.steps[0][0] == "scaler" - assert p.steps[1][0] == "model" - print(f" 全部通过: {list(_MODEL_CLASS_REGISTRY)}") - print(" PASS") - - -def test_get_model_pipeline_bad_type(): - print("== test_get_model_pipeline (坏 model_type) ==") - try: - _get_model_pipeline("XGBoost", {}) - print(" FAIL: 应抛 ValueError") - except ValueError as e: - print(f" 正确抛 ValueError: {e}") - print(" PASS") - - -def test_run_train_sync_rf_end_to_end(): - print("== test_run_train_sync (RF 端到端) ==") - p = make_synthetic_csv(n_samples=200) - out_dir = Path(tempfile.mkdtemp()) - out_path = out_dir / "model.joblib" - - import time - t0 = time.time() - metadata = _run_train_sync( - model_type="RF", - target="Chl-a", - train_data_path=str(p), - feature_start=5, - params={"n_estimators": 30, "max_depth": 6, "random_state": 42, "n_jobs": 1}, - output_model_path=out_path, - ) - dt = time.time() - t0 - - assert out_path.exists(), f"joblib 未落盘: {out_path}" - print(f" joblib 落盘: {out_path} ({out_path.stat().st_size} bytes)") - print(f" metadata.test_r2={metadata['test_r2']:.4f} test_rmse={metadata['test_rmse']:.4f} test_mae={metadata['test_mae']:.4f}") - print(f" metadata.n_features={metadata['n_features']} n_samples={metadata['n_samples']} train_size={metadata['train_size']} test_size={metadata['test_size']}") - print(f" 耗时 {dt:.2f}s") - - # 回测: 加载 joblib 再 predict - import joblib - saved = joblib.load(out_path) - assert "model" in saved and "metadata" in saved, f"joblib 双 key 缺失: {saved.keys()}" - assert hasattr(saved["model"], "predict") - assert saved["metadata"]["test_r2"] == metadata["test_r2"] - print(f" joblib 加载 OK, 含 'model' 和 'metadata' 双 key") - print(" PASS") - - -def test_run_train_sync_linearregression_fast(): - print("== test_run_train_sync (LinearRegression 快速路径) ==") - p = make_synthetic_csv(n_samples=150) - out_path = Path(tempfile.mkdtemp()) / "lr.joblib" - metadata = _run_train_sync( - model_type="LinearRegression", - target="Chl-a", - train_data_path=str(p), - feature_start=5, - params={}, - output_model_path=out_path, - ) - print(f" test_r2={metadata['test_r2']:.4f} (LR 学到线性, R² 应 >= 0.4)") - assert metadata["test_r2"] > 0.3, f"LR test_r2={metadata['test_r2']} 太低, 数据生成可能有问题" - print(" PASS") - - -def test_run_train_sync_bad_csv(): - print("== test_run_train_sync (CSV 不存在) ==") - try: - _run_train_sync("RF", "Chl-a", "/no/such/path.csv", 5, {}, Path("/tmp/x.joblib")) - print(" FAIL: 应抛异常") - except (FileNotFoundError, ValueError) as e: - print(f" 正确抛 {type(e).__name__}: {e}") - print(" PASS") - - -def test_run_train_sync_bad_target(): - print("== test_run_train_sync (target 列不存在) ==") - p = make_synthetic_csv() - try: - _run_train_sync("RF", "NopeTarget", str(p), 5, {}, Path("/tmp/x.joblib")) - print(" FAIL: 应抛 ValueError") - except ValueError as e: - print(f" 正确抛 ValueError: {e}") - print(" PASS") - - -def test_run_train_sync_str_feature_start(): - print("== test_run_train_sync (feature_start 用列名) ==") - p = make_synthetic_csv() - out_path = Path(tempfile.mkdtemp()) / "str_fs.joblib" - metadata = _run_train_sync("RF", "Chl-a", str(p), "feat_0", {"n_estimators": 10}, out_path) - assert metadata["feature_start"] == "feat_0" - assert metadata["n_features"] == 8 - assert metadata["feature_columns"][0] == "feat_0" - print(f" 列名 'feat_0' 解析正确, n_features={metadata['n_features']}") - print(" PASS") - - -if __name__ == "__main__": - test_load_train_df() - test_resolve_feature_start_int_and_str() - test_resolve_feature_start_str_miss() - test_get_model_pipeline_all_types() - test_get_model_pipeline_bad_type() - test_run_train_sync_rf_end_to_end() - test_run_train_sync_linearregression_fast() - test_run_train_sync_bad_csv() - test_run_train_sync_bad_target() - test_run_train_sync_str_feature_start() - print("\n>>> ALL SMOKE TESTS PASSED") diff --git a/new/app/api/endpoints.py b/new/app/api/endpoints.py deleted file mode 100644 index 4a012f1..0000000 --- a/new/app/api/endpoints.py +++ /dev/null @@ -1,222 +0,0 @@ -""" -API 路由集合 -============ - -把业务接口统一收口到 APIRouter,再由 main.py 通过 include_router 挂载。 - -当前包含的接口: - GET /api/algorithms 列出已注册的所有去耀斑算法(供前端下拉框) - POST /api/process/deglint 提交去耀斑处理任务,立即返回 task_id - GET /api/tasks/{task_id} 查询指定任务的状态与结果 - -派发链: - POST /api/process/deglint - └─ BackgroundTasks.add_task(execute_glint_removal_task, ...) - └─ get_remover(method) 从注册表拿到算法类 - └─ remover.process(input_zarr, output_zarr, **params) -""" - -import traceback -import uuid -from datetime import datetime -from typing import Any, Dict - -from fastapi import APIRouter, BackgroundTasks, HTTPException -from pydantic import BaseModel, Field - -# 并发安全的任务状态存储(替代旧版的 MOCK_TASK_DB) -from app.core.task_store import get_task, set_task, update_task - -# 算法注册表 API -from app.core.algorithms import get_remover, list_removers - - -# --------------------------------------------------------------------------- -# 路由实例 -# --------------------------------------------------------------------------- -# prefix 不在此处设置,统一在 main.py 挂载时给定,便于将来按版本拆分 -# (例如 /api/v1、/api/v2 共存时复用同一个 router 对象)。 -# --------------------------------------------------------------------------- -router = APIRouter(tags=["deglint"]) - - -# --------------------------------------------------------------------------- -# 请求 / 响应数据模型 -# --------------------------------------------------------------------------- -class DeglintRequest(BaseModel): - """POST /api/process/deglint 的请求体""" - - method: str = Field( - ..., - description="去耀斑方法名称,必须是已注册算法,例如 'kutser' / 'goodman'", - examples=["kutser"], - ) - params: Dict[str, Any] = Field( - default_factory=dict, - description=( - "传递给算法 process() 的超参数字典,例如 " - "Kutser: {'band_lower': 773, 'band_oxy': 845, 'band_upper': 893}; " - "Goodman: {'band_ref': 750, 'band_diff': 640, 'A': 0.0, 'B': 0.0}" - ), - examples=[{"band_lower": 773, "band_oxy": 845, "band_upper": 893}], - ) - - -class TaskAcceptedResponse(BaseModel): - """提交任务成功后立即返回的响应""" - - task_id: str - status: str # 一定是 PENDING - - -class AlgorithmListResponse(BaseModel): - """GET /api/algorithms 的响应""" - - algorithms: list # 已注册算法名列表 - count: int # 算法总数 - - -# --------------------------------------------------------------------------- -# 后台任务执行器(真实派发链) -# --------------------------------------------------------------------------- -# 注意:这里使用 async def。 -# FastAPI / Starlette 的 BackgroundTasks 支持 async function, -# 会在响应返回后自动 await 它,不影响主请求链路。 -# --------------------------------------------------------------------------- -async def execute_glint_removal_task( - task_id: str, - method: str, - params: Dict[str, Any], -) -> None: - """ - 后台异步执行器:按 method 名字从注册表取出算法类,实例化并运行 process()。 - - 状态机: - PENDING -> PROCESSING -> SUCCESS - └──> FAILED(含 error / traceback) - """ - # 0. 安全检查:任务记录必须已存在(POST 阶段已写入) - record = await get_task(task_id) - if record is None: - print(f"[{task_id}] 任务不存在, 跳过") - return - - # 1. 状态推进到 PROCESSING - await update_task( - task_id, - status="PROCESSING", - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 开始处理 method={method} params={params}") - - # 2. 临时硬编码 IO 路径(未来由数据管理层提供) - # TODO: 替换为真实的数据管理服务返回的 zarr 路径 - input_zarr_path = "./data/temp_in.zarr" - output_zarr_path = f"./data/{task_id}_out.zarr" - - try: - # 3. 按 method 名字从注册表取算法类并实例化 - # get_remover 找不到时会抛 KeyError,下面的 except 会兜住 - algorithm_cls = get_remover(method) - remover = algorithm_cls() - - # 4. 调用算法(注意 await,因为 BaseGlintRemover.process 是 async) - await remover.process(input_zarr_path, output_zarr_path, **params) - - # 5. 成功:写回结果路径与状态 - await update_task( - task_id, - status="SUCCESS", - output_zarr_path=output_zarr_path, - error=None, - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 处理完成 -> SUCCESS, output={output_zarr_path}") - - except Exception as exc: # noqa: BLE001 顶层兜底,绝不让后台任务静默失败 - # 6. 失败:记录错误信息与堆栈,便于前端排查 - await update_task( - task_id, - status="FAILED", - output_zarr_path=None, - error=f"{type(exc).__name__}: {exc}", - traceback=traceback.format_exc(), - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 处理失败 -> {type(exc).__name__}: {exc}") - - -# --------------------------------------------------------------------------- -# GET /algorithms -# --------------------------------------------------------------------------- -# 返回当前已注册的所有算法名,供前端动态渲染下拉框 / 选择器。 -# --------------------------------------------------------------------------- -@router.get("/algorithms", response_model=AlgorithmListResponse) -async def list_registered_algorithms() -> Dict[str, Any]: - """列出已注册的去耀斑算法。""" - names = list(list_removers().keys()) - return {"algorithms": names, "count": len(names)} - - -# --------------------------------------------------------------------------- -# POST /process/deglint -# --------------------------------------------------------------------------- -# 提交去耀斑处理任务。FastAPI 在函数返回后才会把响应发给前端, -# 因此通过 BackgroundTasks 把耗时操作丢到后台,接口本身立刻返回 task_id。 -# --------------------------------------------------------------------------- -@router.post("/process/deglint", response_model=TaskAcceptedResponse) -async def submit_deglint( - payload: DeglintRequest, - background_tasks: BackgroundTasks, -) -> Dict[str, Any]: - """提交一个去耀斑处理任务,并立即返回 task_id。""" - - # 1. 生成唯一任务 ID(UUID4 足以保证全局唯一性) - task_id = str(uuid.uuid4()) - - # 2. 在任务库中登记一条 PENDING 记录(并发安全) - # 注意:output_zarr_path / error / traceback 字段在执行过程中被填充 - await set_task( - task_id, - { - "task_id": task_id, - "method": payload.method, - "params": payload.params, - "status": "PENDING", - "output_zarr_path": None, - "error": None, - "traceback": None, - "created_at": datetime.now().isoformat(), - "updated_at": datetime.now().isoformat(), - }, - ) - - # 3. 把真实执行器丢到后台 - background_tasks.add_task( - execute_glint_removal_task, - task_id, - payload.method, - payload.params, - ) - - # 4. 立即返回 task_id 与 PENDING 状态 - return {"task_id": task_id, "status": "PENDING"} - - -# --------------------------------------------------------------------------- -# GET /tasks/{task_id} -# --------------------------------------------------------------------------- -# 前端轮询此接口获取任务状态。PENDING / PROCESSING 表示仍在跑, -# SUCCESS 表示成功(含 output_zarr_path),FAILED 表示失败(含 error / traceback)。 -# --------------------------------------------------------------------------- -@router.get("/tasks/{task_id}") -async def get_task_status(task_id: str) -> Dict[str, Any]: - """查询指定任务的当前状态与结果。""" - - record = await get_task(task_id) - if record is None: - # 找不到 task_id 通常意味着客户端拼错了 ID,或者记录已被清理 - raise HTTPException(status_code=404, detail=f"task_id 不存在: {task_id}") - - # 直接返回字典,FastAPI 会自动 JSON 序列化 - return record diff --git a/new/app/api/modeling.py b/new/app/api/modeling.py deleted file mode 100644 index 01ac34f..0000000 --- a/new/app/api/modeling.py +++ /dev/null @@ -1,847 +0,0 @@ -""" -app/api/modeling.py -=================== - -机器学习与水质反演相关的 API 路由。 - -接口(最终路径, 挂载后): - POST /api/modeling/train 提交模型训练任务, 立即返回 task_id - GET /api/modeling/models 列出已训练好的模型(未来从磁盘 joblib 读) - POST /api/modeling/predict 提交模型推断任务, 立即返回 task_id - -设计要点 --------- -- 训练 / 推断均为异步后台任务, 复用 app.core.task_store 的并发安全任务状态。 -- 模型元数据用模块级 _MODEL_REGISTRY 暂存(开发期内存存储), - 未来从磁盘 joblib 读时只需替换 list_trained_models() 内部实现即可。 -- /predict 已接入真实 sklearn + xarray + dask 流式推断: - * joblib.load 读模型(缺文件时降级为 Dummy RandomForestRegressor) - * xr.open_zarr 延迟打开影像, NaN 填 0 - * xr.apply_ufunc(dask="parallelized") 沿 (y, x) 逐 chunk 调 model.predict - * to_zarr(mode="w", compute=True) 流式写出, 内存峰值 ≈ 1 个 chunk -- /train 已接入真实 sklearn + pandas 训练管线: - * pd.read_csv 读结构化训练表(支持 [lat, lon, target_*, feature_*] 布局) - * 按 target 列 dropna 清洗;按 feature_start 索引/列名切分特征 - * sklearn Pipeline: StandardScaler -> {RF/SVR/LinearRegression/KNN/PLS} - * train_test_split(80/20) 划分, 计算 test_r2/rmse/mae - * joblib.dump({model, metadata}) 落盘 ./data/models/{model_id}.joblib - * 测试指标写回 TASK_STORE, 同时登记到 _MODEL_REGISTRY - 注: 旧版 SPXY / KS 划分留作未来扩展, 当前固定 random 划分 (test_size=0.2, random_state=42)。 -""" - -import asyncio -import shutil -import traceback -import uuid -from datetime import datetime -from pathlib import Path -from typing import Any, Dict, List, Optional, Union - -import joblib -import numpy as np -import pandas as pd -import xarray as xr -from fastapi import APIRouter, BackgroundTasks, HTTPException, UploadFile, File -from pydantic import BaseModel, Field -from sklearn.cross_decomposition import PLSRegression -from sklearn.ensemble import RandomForestRegressor -from sklearn.linear_model import LinearRegression -from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score -from sklearn.model_selection import train_test_split -from sklearn.neighbors import KNeighborsRegressor -from sklearn.pipeline import Pipeline -from sklearn.preprocessing import StandardScaler -from sklearn.svm import SVR - -# 复用并发安全任务状态存储(与 deglint 共享同一份 TASK_STORE, -# 通过 task 记录里的 "kind" 字段区分 train / predict / deglint) -from app.core.task_store import get_task, set_task, update_task - - -# --------------------------------------------------------------------------- -# 路由实例 -# --------------------------------------------------------------------------- -# prefix="/modeling" 让本文件内只写 /train /models /predict 等短路径, -# 最终完整路径由 main.py 挂载时再补 /api。 -# --------------------------------------------------------------------------- -router = APIRouter(prefix="/modeling", tags=["modeling"]) - - -# --------------------------------------------------------------------------- -# 数据模型 -# --------------------------------------------------------------------------- -class TrainRequest(BaseModel): - """POST /api/modeling/train 的请求体""" - - model_type: str = Field( - ..., - description="模型类型, 例如 'RF' (随机森林) / 'SVR' (支持向量回归) / 'XGBoost' / 'MLP'", - examples=["RF", "SVR"], - ) - target: str = Field( - ..., - description="反演目标水质参数, 例如 'Chl-a' (叶绿素a) / 'TSS' (总悬浮物) / 'CDOM' (有色可溶有机物)", - examples=["Chl-a", "TSS", "CDOM"], - ) - train_data_path: str = Field( - ..., - description="训练数据集的 zarr 路径(包含 reflectance 变量与 target 标签)", - examples=["./data/train.zarr"], - ) - feature_start: Union[int, str] = Field( - default=4, - description=( - "特征列起始位置. 表格布局假定为 " - "[lat, lon, target_1, target_2, ..., feature_1, feature_2, ...] " - "可传 int 列索引(如 4)或 str 列名(如 '374.285' 波长起点)。" - "默认 4, 即前 4 列视为元数据/目标, 之后全部是特征。" - ), - examples=[4, "374.285"], - ) - params: Dict[str, Any] = Field( - default_factory=dict, - description="模型超参, 例如 RF 的 {'n_estimators': 100, 'max_depth': 20}", - examples=[{"n_estimators": 100, "max_depth": 20}], - ) - - -class PredictRequest(BaseModel): - """POST /api/modeling/predict 的请求体""" - - model_id: str = Field( - ..., - description="已训练模型的 ID(由 /api/modeling/train 返回或 /api/modeling/models 列出)", - ) - input_zarr_path: str = Field( - ..., - description="待推断影像的 zarr 路径", - examples=["./data/scene.zarr"], - ) - output_zarr_path: Optional[str] = Field( - default=None, - description=( - "输出 zarr 路径, 缺省时由后端按规则生成 " - "(如 ./data/{model_id}_{input_stem}_pred.zarr)" - ), - ) - - -class TaskAcceptedResponse(BaseModel): - """提交训练/推断任务后立即返回的响应""" - - task_id: str - status: str # 一定是 PENDING - kind: str # "train" / "predict", 便于前端识别任务类型 - - -class ModelInfo(BaseModel): - """单个模型的元信息(GET /api/modeling/models 的元素)""" - - model_id: str - model_type: str - target: str - params: Dict[str, Any] - path: str # joblib 文件路径 - created_at: str - train_task_id: str # 产生此模型的那个训练任务的 ID - - -class ModelListResponse(BaseModel): - """GET /api/modeling/models 的响应""" - - models: List[ModelInfo] - count: int - - -# --------------------------------------------------------------------------- -# 模块级模型注册表(开发期内存, 未来替换为磁盘扫描) -# --------------------------------------------------------------------------- -# model_id -> ModelInfo 字典 -# 读多写少, 用一个普通 dict 足够(CPython GIL 兜底)。 -# 写时(训练完成时)只发生一次, 无并发风险。 -# --------------------------------------------------------------------------- -_MODEL_REGISTRY: Dict[str, Dict[str, Any]] = {} - - -def _register_model(record: Dict[str, Any]) -> None: - """将训练完成的模型登记到内存注册表。""" - _MODEL_REGISTRY[record["model_id"]] = record - - -# --------------------------------------------------------------------------- -# 训练管线的模块级辅助函数 -# --------------------------------------------------------------------------- -# 设计要点 (与推断管线一致): -# 1) 模块级函数: dask / joblib 后端若走子进程 pickle, 嵌套闭包会丢字段。 -# 2) 同步执行: execute_train_task 用 asyncio.to_thread 派发, 内部全程同步阻塞。 -# 3) 失败抛异常: 异常由 execute_train_task 捕获, 转 FAILED + traceback。 -# --------------------------------------------------------------------------- - -# model_type (大写字符串) -> sklearn 估计器类 -# 与 OpenClaw model_configs 思路一致, 但此处只保留类 (参数由 params 透传) -_MODEL_CLASS_REGISTRY: Dict[str, type] = { - "RF": RandomForestRegressor, - "SVR": SVR, - "LinearRegression": LinearRegression, - "KNN": KNeighborsRegressor, - "PLS": PLSRegression, -} - - -def _get_model_pipeline(model_type: str, params: Optional[Dict[str, Any]]) -> Pipeline: - """ - 模型工厂: 按 model_type 选 sklearn 类, 用 StandardScaler + 估计器构造 Pipeline。 - - 与 OpenClaw 不同之处: 把 scaler 放进 Pipeline 第一步, - 推断时直接 pipeline.predict(X) 即可, scaler 参数与训练时严格一致。 - """ - model_cls = _MODEL_CLASS_REGISTRY.get(model_type) - if model_cls is None: - raise ValueError( - f"不支持的 model_type='{model_type}', " - f"可选: {sorted(_MODEL_CLASS_REGISTRY.keys())}" - ) - estimator = model_cls(**(params or {})) - return Pipeline([("scaler", StandardScaler()), ("model", estimator)]) - - -def _load_train_df(csv_path: str) -> pd.DataFrame: - """ - 读 CSV 训练表, 规整空串 / 空白 / NULL 等为 NaN。 - - 沿用 OpenClaw modeling_batch.load_data_batch 的读取策略: - na_values 显式列举 + 正则二次清理 (防 cell 内出现 " " 等纯空白)。 - """ - try: - df = pd.read_csv( - csv_path, - na_values=["", " ", "NaN", "nan", "NULL", "null"], - ) - except FileNotFoundError as exc: - raise FileNotFoundError(f"训练数据文件不存在: {csv_path}") from exc - except pd.errors.EmptyDataError as exc: - raise ValueError(f"训练数据文件为空: {csv_path}") from exc - # 二次清理: 残留的纯空白 cell - df = df.replace(r"^\s*$", np.nan, regex=True) - return df - - -def _resolve_feature_start( - df: pd.DataFrame, - feature_start: Union[int, str], -) -> int: - """ - 将 feature_start (int 索引 / str 列名) 统一解析为 int 列索引。 - - 与 OpenClaw modeling_batch.load_data_batch / load_data_single 一致: - str 走 columns.get_loc, int 直接返回。 - """ - if isinstance(feature_start, str): - if feature_start not in df.columns: - raise ValueError( - f"feature_start='{feature_start}' 不在 CSV 列中: {list(df.columns)}" - ) - return int(df.columns.get_loc(feature_start)) - return int(feature_start) - - -def _run_train_sync( - model_type: str, - target: str, - train_data_path: str, - feature_start: Union[int, str], - params: Optional[Dict[str, Any]], - output_model_path: Path, -) -> Dict[str, Any]: - """ - 完整同步训练流程 (由 execute_train_task 在线程池内调用): - - pd.read_csv -> 目标列 dropna -> 切特征 -> train_test_split(80/20) - -> Pipeline(StandardScaler + model).fit -> 评估 test_r2/rmse/mae - -> joblib.dump({model, metadata}, output_model_path) - - Returns: - metadata 字典, 含 test_r2 / test_rmse / test_mae / n_features 等, - 调用方负责写回 TASK_STORE 和 _MODEL_REGISTRY。 - - 注: 旧版 SPXY / KS 划分留作未来扩展 (params.split_method 控制), - 当前固定 random + test_size=0.2 + random_state=42。 - """ - df = _load_train_df(train_data_path) - - if target not in df.columns: - raise ValueError( - f"target='{target}' 不在 CSV 列中, 可选: {list(df.columns)}" - ) - - # 1) 清洗: 仅剔除 target NaN 的行 (与 OpenClaw load_data_single 一致) - df = df[df[target].notna()].copy() - if df.empty: - raise ValueError("target 剔除 NaN 后无样本, 终止训练") - - # 2) 特征切分 - feature_start_idx = _resolve_feature_start(df, feature_start) - feature_columns = list(df.columns[feature_start_idx:]) - - X = df.iloc[:, feature_start_idx:].astype(np.float64) - y = df[target].astype(np.float64).values - - # 3) 划分 (固定 random, 未来扩展 spxy/ks) - X_train, X_test, y_train, y_test = train_test_split( - X.values, - y, - test_size=0.2, - random_state=42, - ) - - # 4) 构造 Pipeline + 训练 - pipeline = _get_model_pipeline(model_type, params) - pipeline.fit(X_train, y_train) - - # 5) 测试集与训练集评估 - y_pred = pipeline.predict(X_test) - test_r2 = float(r2_score(y_test, y_pred)) - test_rmse = float(np.sqrt(mean_squared_error(y_test, y_pred))) - test_mae = float(mean_absolute_error(y_test, y_pred)) - - y_train_pred = pipeline.predict(X_train) - train_r2 = float(r2_score(y_train, y_train_pred)) - train_rmse = float(np.sqrt(mean_squared_error(y_train, y_train_pred))) - train_mae = float(mean_absolute_error(y_train, y_train_pred)) - - metadata: Dict[str, Any] = { - "model_type": model_type, - "target": target, - "feature_start": feature_start, - "feature_columns": feature_columns, - "n_features": int(X.shape[1]), - "n_samples": int(X.shape[0]), - "train_size": int(X_train.shape[0]), - "test_size": int(X_test.shape[0]), - "params": dict(params or {}), - "test_r2": test_r2, - "test_rmse": test_rmse, - "test_mae": test_mae, - "train_r2": train_r2, - "train_rmse": train_rmse, - "train_mae": train_mae, - "split_method": "random", - "trained_at": datetime.now().isoformat(), - } - - # 7) 持久化 (目录可能不存在, 顺手建) - output_model_path = Path(output_model_path) - output_model_path.parent.mkdir(parents=True, exist_ok=True) - joblib.dump( - {"model": pipeline, "metadata": metadata}, - output_model_path, - ) - - return metadata - - -# --------------------------------------------------------------------------- -# 推断管线的模块级辅助函数 -# --------------------------------------------------------------------------- -# 设计要点: -# 1) Dask 调度时, 函数必须可被工作进程 pickle 序列化。 -# 因此 _predict_block / _load_model / _make_dummy_model / _run_predict_sync -# 全部是模块级函数 (而非嵌套), 避免闭包陷阱。 -# 2) _predict_block 通过 model.predict(spectra_2d) 整批预测, -# 整张影像的 O(n_pixels * n_bands) 一次性预测在大矩阵上必 OOM, -# 因此外层用 xr.apply_ufunc(dask="parallelized") 把矩阵切块 -# 逐块进入此函数, 单次内存峰值 ≈ 1 个 (y_chunk, x_chunk, band) 大小。 -# --------------------------------------------------------------------------- - - -def _make_dummy_model(n_features: int) -> RandomForestRegressor: - """ - 构造一个 Dummy 随机森林回归器。 - - 用途: - 1) 真实 joblib 文件不存在时的连通性测试 - 2) 训练骨架尚未接入真实数据时的占位推断 - """ - rng = np.random.default_rng(42) - X = rng.random((200, n_features)) - y = rng.random(200) - model = RandomForestRegressor( - n_estimators=10, max_depth=5, random_state=0, n_jobs=1 - ) - model.fit(X, y) - return model - - -def _load_model(path: str, n_features: int) -> Any: - """ - 加载训练好的 sklearn 模型, 失败时降级 Dummy。 - - 优先级: - 1) path 存在且 joblib.load 成功 -> 返回真实模型 - 2) 否则 -> 降级为 Dummy 随机森林 (n_features 必须指定) - """ - p = Path(path) - if p.is_file() and p.stat().st_size > 0: - try: - print(f"[model] 从磁盘加载: {path}") - return joblib.load(path) - except Exception as exc: # noqa: BLE001 - print(f"[model] joblib.load 失败 ({type(exc).__name__}: {exc}), 降级 Dummy") - print(f"[model] 真实 joblib 不存在 ({path}), 使用 Dummy RandomForest") - return _make_dummy_model(n_features) - - -def _predict_block(spectra_3d: np.ndarray, model: Any) -> np.ndarray: - """ - 单个 dask chunk 的推断函数 (xr.apply_ufunc 会自动调度调用)。 - - Parameters - ---------- - spectra_3d : np.ndarray - 形状 (y_chunk, x_chunk, n_bands)。 - 此形状由 input_core_dims=[["band"]] 决定: - xarray 会把 band 维移到最后一轴, 然后按 (y, x) 的 chunk 切分调用本函数。 - model : 已 fit 好的 sklearn 估计器 - 接受 (n_samples, n_features) 输入, 返回 (n_samples,) 预测。 - - Returns - ------- - np.ndarray - 形状 (y_chunk, x_chunk), dtype float32 的标量预测图。 - """ - yc, xc, nb = spectra_3d.shape - # 2D 化: 每个像素一行光谱 - flat = spectra_3d.reshape(yc * xc, nb) - # sklearn 风格的批量预测 - pred = model.predict(flat) - # 还原为 2D 空间图, 强制 float32 节约一半内存 - return pred.reshape(yc, xc).astype(np.float32, copy=False) - - -def _run_predict_sync( - model: Any, - model_id: str, - input_zarr_path: str, - output_zarr_path: str, -) -> None: - """ - 同步推断主流程 (被 asyncio.to_thread 调用)。 - - 流程: - 1) xr.open_zarr 延迟打开 (dask 数组, 不一次性读入内存) - 2) NaN -> 0 清洗 (model.predict 不接受 NaN) - 3) xr.apply_ufunc 沿 (y, x) 逐 chunk 调 _predict_block - 4) 非水域置 NaN (zarr 支持 float NaN) - 5) to_zarr 触发整图计算 + 流式写出 - """ - # 1. 延迟打开输入 (关键: Dask 不一次性读入内存) - ds = xr.open_zarr(input_zarr_path, chunks="auto") - if "reflectance" not in ds.data_vars: - raise KeyError( - f"输入 zarr 缺少 'reflectance' 变量; 实际: {list(ds.data_vars)}" - ) - - reflectance = ds["reflectance"] # dims: (y, x, band) - n_bands = reflectance.sizes["band"] - - # 2. 水域掩膜 (与去耀斑算法同约定) - if "water_mask" in ds.data_vars or "water_mask" in ds.coords: - water_mask = ds["water_mask"].astype(bool) - else: - water_mask = xr.ones_like(reflectance.isel(band=0), dtype=bool) - - # 3. NaN 清洗: 填充 0 (model.predict 不接受 NaN) - refl_clean = reflectance.fillna(0.0) - - # 4. 核心: 用 apply_ufunc 把 model.predict 沿 (y, x) 应用 - # dask="parallelized" 让每个 (y_chunk, x_chunk, band) chunk - # 独立调 _predict_block, 任意时刻内存中只有若干个 chunk。 - prediction: xr.DataArray = xr.apply_ufunc( - _predict_block, - refl_clean, - kwargs={"model": model}, - input_core_dims=[["band"]], - output_core_dims=[[]], - dask="parallelized", - output_dtypes=[np.float32], - dask_gufunc_kwargs={"allow_rechunk": True}, - vectorize=False, - ) - - # 5. 非水域置 NaN (zarr 支持 float NaN, 便于后续可视化/掩膜分析) - prediction = prediction.where(water_mask, np.nan) - - # 6. 包装为 Dataset 并流式写出 - out = xr.Dataset( - {"prediction": prediction}, - attrs={ - "model_id": model_id, - "input_zarr_path": input_zarr_path, - "n_bands": n_bands, - "created_at": datetime.now().isoformat(), - }, - ) - # 保留 y/x 坐标 - out = out.assign_coords(y=ds["y"], x=ds["x"]) - - # to_zarr + compute=True 触发整图 dask 图求值 - # 中间会按 chunk 逐块调度到线程池, 内存峰值 ≈ 1 个 chunk 的体量 - out.to_zarr(output_zarr_path, mode="w", compute=True) - - -# --------------------------------------------------------------------------- -# 后台任务执行器 -# --------------------------------------------------------------------------- -async def execute_train_task( - task_id: str, - model_type: str, - target: str, - train_data_path: str, - feature_start: Union[int, str], - params: Dict[str, Any], -) -> None: - """ - 训练任务后台执行器(已接入真实 sklearn 训练流程)。 - - 流程: - 1) get_task 校验任务存在 - 2) update_task(PROCESSING) - 3) 生成 model_id / model_path - 4) asyncio.to_thread 派发 _run_train_sync 到默认线程池 - 5) 成功 -> _register_model + update_task(SUCCESS, 附 test_r2/rmse/mae) - 6) 失败 -> update_task(FAILED, 附 error + traceback) - """ - record = await get_task(task_id) - if record is None: - print(f"[{task_id}] 训练任务不存在, 跳过") - return - - await update_task( - task_id, - status="PROCESSING", - updated_at=datetime.now().isoformat(), - ) - print( - f"[{task_id}] 开始训练 model_type={model_type} target={target} " - f"train_data_path={train_data_path} feature_start={feature_start}" - ) - - # model_id 用 uuid4 前 12 位 (8 位易撞, 12 位兼顾可读性) - model_id = f"model_{uuid.uuid4().hex[:12]}" - model_path = Path(f"./data/models/{model_id}.joblib") - - try: - # 同步 sklearn / pandas 训练丢到默认线程池, 不阻塞 event loop - metadata = await asyncio.to_thread( - _run_train_sync, - model_type, - target, - train_data_path, - feature_start, - params, - model_path, - ) - - # 登记到内存注册表 (供 /predict 查 model_id) - _register_model( - { - "model_id": model_id, - "model_type": model_type, - "target": target, - "params": dict(params or {}), - "path": str(model_path), - "feature_start": feature_start, - "n_features": metadata["n_features"], - "test_r2": metadata["test_r2"], - "test_rmse": metadata["test_rmse"], - "test_mae": metadata["test_mae"], - "created_at": datetime.now().isoformat(), - "train_task_id": task_id, - } - ) - - # 把训练指标写回任务记录, 前端轮询时可直接看 - await update_task( - task_id, - status="SUCCESS", - model_id=model_id, - model_path=str(model_path), - test_r2=metadata["test_r2"], - test_rmse=metadata["test_rmse"], - test_mae=metadata["test_mae"], - n_features=metadata["n_features"], - n_samples=metadata["n_samples"], - error=None, - traceback=None, - updated_at=datetime.now().isoformat(), - ) - print( - f"[{task_id}] 训练完成 -> model_id={model_id} " - f"test_r2={metadata['test_r2']:.4f} test_rmse={metadata['test_rmse']:.4f}" - ) - - except Exception as exc: # noqa: BLE001 - # 失败时 model_path 不一定有产物, 显式置 None 方便前端判断 - await update_task( - task_id, - status="FAILED", - model_id=None, - model_path=None, - error=f"{type(exc).__name__}: {exc}", - traceback=traceback.format_exc(), - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 训练失败 -> {type(exc).__name__}: {exc}") - - -async def execute_predict_task( - task_id: str, - model_id: str, - input_zarr_path: str, - output_zarr_path: Optional[str], -) -> None: - """ - 推断任务后台执行器(真实实现版)。 - - OOM 防护策略: - - xr.open_zarr(..., chunks="auto") 延迟打开, 整图不一次性读入内存 - - xr.apply_ufunc(..., dask="parallelized") 把影像按 chunk 切分 - - 每个 chunk 内部 reshape 成 2D, 调 model.predict, 再 reshape 回 2D - - 任意时刻内存峰值 ≈ 1 个 (y_chunk, x_chunk, band) chunk 的体量 - - 整图完成计算后再 to_zarr(compute=True) 流式写出 - """ - record = await get_task(task_id) - if record is None: - print(f"[{task_id}] 推断任务不存在, 跳过") - return - - # 1. 校验 model_id 是否已注册 (避免在后台任务里报模糊错误) - model_meta = _MODEL_REGISTRY.get(model_id) - if model_meta is None: - await update_task( - task_id, - status="FAILED", - error=f"model_id 不存在: {model_id}", - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 推断失败 -> model_id 不存在: {model_id}") - return - - # 2. 自动生成 output_zarr_path (若未提供) - if output_zarr_path is None: - stem = input_zarr_path.rstrip("/\\").split("/")[-1].split("\\")[-1] - stem = stem.replace(".zarr", "") - output_zarr_path = f"./data/{model_id}_{stem}_pred.zarr" - - await update_task( - task_id, - status="PROCESSING", - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 开始推断 model_id={model_id} input={input_zarr_path}") - - try: - # 3. 探测波段数 (用于 Dummy 模型适配) - # 这里只读 zarr 元数据 (.zarray 的 shape), 不读真实数据 - ds_probe = xr.open_zarr(input_zarr_path, chunks="auto") - if "reflectance" not in ds_probe.data_vars: - raise KeyError( - f"输入 zarr 缺少 'reflectance' 变量; 实际: {list(ds_probe.data_vars)}" - ) - n_bands = ds_probe["reflectance"].sizes["band"] - ds_probe.close() - - # 4. 加载模型 (真实文件优先, Dummy 兜底) - model = _load_model(model_meta["path"], n_features=n_bands) - - # 5. 包装同步执行, 丢到线程池, 事件循环不阻塞 - await asyncio.to_thread( - _run_predict_sync, - model, - model_id, - input_zarr_path, - output_zarr_path, - ) - - await update_task( - task_id, - status="SUCCESS", - output_zarr_path=output_zarr_path, - model_id=model_id, - error=None, - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 推断完成 -> output={output_zarr_path}") - - except Exception as exc: # noqa: BLE001 - tb_text = traceback.format_exc() - await update_task( - task_id, - status="FAILED", - output_zarr_path=None, - error=f"{type(exc).__name__}: {exc}", - traceback=tb_text, - updated_at=datetime.now().isoformat(), - ) - print(f"[{task_id}] 推断失败 -> {type(exc).__name__}: {exc}") - print(tb_text) - - -# --------------------------------------------------------------------------- -# POST /api/modeling/train -# --------------------------------------------------------------------------- -@router.post("/train", response_model=TaskAcceptedResponse) -async def submit_train( - payload: TrainRequest, - background_tasks: BackgroundTasks, -) -> Dict[str, Any]: - """提交一个模型训练任务, 立即返回 task_id。""" - - task_id = str(uuid.uuid4()) - await set_task( - task_id, - { - "task_id": task_id, - "kind": "train", - "model_type": payload.model_type, - "target": payload.target, - "train_data_path": payload.train_data_path, - "feature_start": payload.feature_start, - "params": payload.params, - "status": "PENDING", - "model_id": None, - "model_path": None, - "test_r2": None, - "test_rmse": None, - "test_mae": None, - "n_features": None, - "n_samples": None, - "error": None, - "traceback": None, - "created_at": datetime.now().isoformat(), - "updated_at": datetime.now().isoformat(), - }, - ) - background_tasks.add_task( - execute_train_task, - task_id, - payload.model_type, - payload.target, - payload.train_data_path, - payload.feature_start, - payload.params, - ) - return {"task_id": task_id, "status": "PENDING", "kind": "train"} - - -# --------------------------------------------------------------------------- -# GET /api/modeling/models -# --------------------------------------------------------------------------- -@router.get("/models", response_model=ModelListResponse) -async def list_trained_models() -> Dict[str, Any]: - """ - 列出已训练好的模型。 - - 未来实现: 从 ./data/models/*.joblib 扫描元信息, - 当前直接从内存 _MODEL_REGISTRY 读。 - """ - models = list(_MODEL_REGISTRY.values()) - # 按 created_at 倒序, 最新训练的在前 - models.sort(key=lambda m: m.get("created_at", ""), reverse=True) - return {"models": models, "count": len(models)} - - -# --------------------------------------------------------------------------- -# POST /api/modeling/predict -# --------------------------------------------------------------------------- -@router.post("/predict", response_model=TaskAcceptedResponse) -async def submit_predict( - payload: PredictRequest, - background_tasks: BackgroundTasks, -) -> Dict[str, Any]: - """提交一个模型推断任务, 立即返回 task_id。""" - - task_id = str(uuid.uuid4()) - await set_task( - task_id, - { - "task_id": task_id, - "kind": "predict", - "model_id": payload.model_id, - "input_zarr_path": payload.input_zarr_path, - "output_zarr_path": payload.output_zarr_path, - "status": "PENDING", - "error": None, - "traceback": None, - "created_at": datetime.now().isoformat(), - "updated_at": datetime.now().isoformat(), - }, - ) - background_tasks.add_task( - execute_predict_task, - task_id, - payload.model_id, - payload.input_zarr_path, - payload.output_zarr_path, - ) - return {"task_id": task_id, "status": "PENDING", "kind": "predict"} - - -# --------------------------------------------------------------------------- -# models_router — 独立于 modeling_router,路径前缀为 /models -# 最终完整路径: GET /api/models, POST /api/models/upload -# --------------------------------------------------------------------------- -models_router = APIRouter(prefix="/models", tags=["models"]) - - -# --------------------------------------------------------------------------- -# GET /api/models -# --------------------------------------------------------------------------- -@models_router.get("") -async def list_models() -> Dict[str, Any]: - """ - 扫描 ./data/models/ 目录,返回所有 .joblib 文件名(不含后缀)。 - - 异常处理:目录不存在时自动创建,返回空列表。 - """ - models_dir = Path("./data/models") - models_dir.mkdir(parents=True, exist_ok=True) - - model_names = [ - p.stem for p in models_dir.iterdir() if p.suffix == ".joblib" - ] - return {"models": model_names} - - -# --------------------------------------------------------------------------- -# POST /api/models/upload -# --------------------------------------------------------------------------- -@models_router.post("/upload") -async def upload_model( - file: UploadFile = File(...), -) -> Dict[str, Any]: - """ - 接收上传的 .joblib 模型文件,保存到 ./data/models/ 目录。 - - - 校验后缀必须为 .joblib - - 目录不存在时自动创建 - - 返回状态和文件名(不含后缀) - """ - if not file.filename or not file.filename.lower().endswith(".joblib"): - raise HTTPException( - status_code=400, - detail="仅支持 .joblib 格式的文件", - ) - - models_dir = Path("./data/models") - models_dir.mkdir(parents=True, exist_ok=True) - - dest_path = models_dir / file.filename - - with dest_path.open("wb") as buffer: - shutil.copyfileobj(file.file, buffer) - - return { - "status": "success", - "model_id": dest_path.stem, - } diff --git a/new/app/core/algorithms/__init__.py b/new/app/core/algorithms/__init__.py deleted file mode 100644 index 6cd03b7..0000000 --- a/new/app/core/algorithms/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -去耀斑算法包 -============ - -通过「注册表 + 策略模式」组织不同的去耀斑算法。 -所有具体算法都应继承 BaseGlintRemover,并使用 @register_glint_remover -装饰器把算法名和实现类绑定。 - -外部调用约定 ------------- -1. 所有算法子模块必须在本 __init__ 中显式 import, - 这样装饰器才会被执行、注册表才会被填满。 -2. 上层(endpoints、worker)只允许: - from app.core.algorithms import get_remover - 来获取算法类,不要直接 import 具体实现类, - 保持调度层与具体算法的解耦。 -""" - -from app.core.algorithms.base import BaseGlintRemover -from app.core.algorithms.registry import ( - get_remover, - list_removers, - register_glint_remover, - unregister_glint_remover, -) - -# ---- 算法子模块 import 区 ---- -# 新增算法时,在这里加一行 import,确保装饰器被执行。 -from app.core.algorithms import goodman # Goodman -from app.core.algorithms import kutser # Kutser -# from app.core.algorithms import hedley # Hedley -# from app.core.algorithms import sugar # SUGAR - -__all__ = [ - "BaseGlintRemover", - "register_glint_remover", - "get_remover", - "list_removers", - "unregister_glint_remover", -] diff --git a/new/app/core/algorithms/base.py b/new/app/core/algorithms/base.py deleted file mode 100644 index 47bce25..0000000 --- a/new/app/core/algorithms/base.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -去耀斑算法抽象基类 -================== - -设计目标(策略模式 Strategy Pattern) ------------------------------------- -本模块定义了所有去耀斑算法必须遵守的标准接口。 -未来的 Kutser、Goodman、Hedley、SUGAR 等算法都将继承本基类, -并实现统一的 process() 方法。 - -输入输出规范 ------------- -所有算法的输入与输出均统一为 **Zarr 文件路径**(字符串), -而不是内存中的 numpy ndarray。这样做的核心收益是: - - 1. **解耦数据存储与内存计算**: - 算法只关心「从哪个 zarr 读、写到哪个 zarr」, - 至于数据最初来自 GeoTIFF / HDF5 / NetCDF / 内存数组, - 都由 IO 层负责归一化转为 zarr。 - 2. **支持 Out-of-Core 计算**: - 影像往往超过内存上限,zarr 分块(chunk)天然支持按块读取, - 算法实现可以借助 dask / xarray 进行流式计算。 - 3. **可缓存、可复用**: - 中间产物落盘后,下游算法(大气校正、辐射定标)能直接消费, - 避免重复 IO。 - 4. **易于并行与分布式**: - 任务调度层只需把两个路径扔给 worker,无需关心数据细节。 - -约定 ----- -- 子类应实现 process(),完成「读 -> 计算 -> 写」的完整流程。 -- process() 返回 True 表示成功,False 表示失败。 -- 失败时建议抛出异常而非仅返回 False,便于上层 BackgroundTasks 捕获并写入 error 字段。 -""" - -from abc import ABC, abstractmethod -from typing import Any - - -class BaseGlintRemover(ABC): - """ - 去耀斑算法抽象基类。 - - 所有具体算法(Kutser / Goodman / Hedley / SUGAR …)必须继承本类并实现 process()。 - 子类可在 __init__ 中接收自己的超参数(如参考波段、阈值等), - 真正的输入输出数据则由 process() 的两个 zarr 路径参数指定。 - """ - - # 子类可覆盖的算法名称标识,用于调度层按 method 名字查找 - name: str = "base" - - @abstractmethod - async def process( - self, - input_zarr_path: str, - output_zarr_path: str, - **kwargs: Any, - ) -> bool: - """ - 执行去耀斑处理。 - - Parameters - ---------- - input_zarr_path : str - 输入高光谱影像的 zarr 存储路径。 - 数据已由 IO 层完成格式归一化(波段、坐标系、空间维度均已对齐)。 - output_zarr_path : str - 处理结果(去耀斑后影像)的 zarr 存储路径。 - 子类需自行创建该 zarr 存储并写入结果。 - **kwargs : Any - 算法的可选超参数,例如: - - reference_band: 参考近红外波段索引 - - chunk_size: 计算分块大小 - - 其它算法特定参数 - - Returns - ------- - bool - True 表示处理成功,False 表示失败。 - 建议在出错时直接 raise,由调用方统一记录到任务状态。 - """ - raise NotImplementedError - - def __repr__(self) -> str: # pragma: no cover - 调试辅助 - return f"<{self.__class__.__name__} name={self.name!r}>" diff --git a/new/app/core/algorithms/goodman.py b/new/app/core/algorithms/goodman.py deleted file mode 100644 index c6f3ecf..0000000 --- a/new/app/core/algorithms/goodman.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -app/core/algorithms/goodman.py -=============================== - -Goodman et al. 2008 去耀斑算法的 xarray + dask 流式实现。 - -算法公式 --------- - R_corrected = R_raw - R_750 + A + B * (R_640 - R_750) - -其中: - R_raw -- 原始反射率 (y, x, band) - R_750 -- λ=750 nm 处的反射率(红外参考波段, 远离水汽吸收) - R_640 -- λ=640 nm 处的反射率(可见光差异波段) - A, B -- 经验回归参数(用户可通过 params 传入, 默认全 0) - -后处理 ------- -- 负值截断为 0(Clamp to 0) -- 仅在水域掩膜 (water_mask) 内生效, 水外置 0 - -维度约定 --------- - reflectance: (y, x, band), band 坐标通常为 wavelength (nm) - water_mask : (y, x), 布尔类型, True = 水域 -""" - -import asyncio -from typing import Any - -import xarray as xr - -from app.core.algorithms.base import BaseGlintRemover -from app.core.algorithms.registry import register_glint_remover - - -# --------------------------------------------------------------------------- -# 默认参数 -# --------------------------------------------------------------------------- -# 与原始 Goodman 2008 论文符号保持一致, 方便用户交叉对照。 -# A、B 通常通过对纯净深水区做 (R_corr - R_raw) ~ (R_640 - R_750) 回归得到; -# 在缺乏先验知识时, 退化为 A=0, B=0 即等价于 R_corrected = clip(R_raw - R_750, 0)。 -# --------------------------------------------------------------------------- -DEFAULT_BAND_REF: float = 750.0 # λ_750 nm, 红外参考波段 -DEFAULT_BAND_DIFF: float = 640.0 # λ_640 nm, 可见光差异波段 -DEFAULT_A: float = 0.0 # 公式中的常数偏移项 -DEFAULT_B: float = 0.0 # 公式中的斜率项 - - -@register_glint_remover("goodman") -class GoodmanGlintRemover(BaseGlintRemover): - """Goodman et al. 2008 去耀斑算法""" - - name = "goodman" - - async def process( - self, - input_zarr_path: str, - output_zarr_path: str, - **kwargs: Any, - ) -> bool: - # 1. 解析超参数(带默认值, 方便用户按需覆盖) - band_ref: float = kwargs.get("band_ref", DEFAULT_BAND_REF) - band_diff: float = kwargs.get("band_diff", DEFAULT_BAND_DIFF) - A: float = kwargs.get("A", DEFAULT_A) - B: float = kwargs.get("B", DEFAULT_B) - - # 2. 把同步的 xarray/dask 计算丢到工作线程, - # 避免阻塞 FastAPI 的事件循环 - return await asyncio.to_thread( - self._process_sync, - input_zarr_path, - output_zarr_path, - band_ref, - band_diff, - A, - B, - ) - - @staticmethod - def _process_sync( - input_zarr_path: str, - output_zarr_path: str, - band_ref: float, - band_diff: float, - A: float, - B: float, - ) -> bool: - # 1. 以 zarr 路径打开(dask-backed, 不物化到内存) - # chunks="auto" 让 dask 根据每条坐标轴的大小自动决定分块 - ds = xr.open_zarr(input_zarr_path, chunks="auto") - reflectance = ds["reflectance"] # (y, x, band) - - # 2. 用 sel + method='nearest' 提取两个关键波段 - # 返回形状 (y, x), 后续与 (y, x, band) 算术时会自动广播 - R_750 = reflectance.sel(band=band_ref, method="nearest") - R_640 = reflectance.sel(band=band_diff, method="nearest") - - # 3. Goodman 公式: xarray 沿 band 维度自动广播 - # R_corr = R_raw - R_750 + A + B * (R_640 - R_750) - result = reflectance - R_750 + A + B * (R_640 - R_750) - - # 4. 负值截断为 0(clip(min=0) 优于 where(>0, 0, _): - # 不构造布尔中间数组, 底层走 dask 矢量化 clip 路径) - result = result.clip(min=0) - - # 5. 仅在水域内生效(水外强制为 0) - # 优先从 zarr 内部读 water_mask 变量, 缺失则视为全图水域 - if "water_mask" in ds: - water_mask = ds["water_mask"].astype(bool) - result = result.where(water_mask, 0) - - # 6. 构造输出 Dataset, 保留元信息(波段坐标/属性等) - out = xr.Dataset({"reflectance": result}) - if ds.attrs: - out.attrs = dict(ds.attrs) - if reflectance.attrs: - out["reflectance"].attrs = dict(reflectance.attrs) - - # 7. 流式写出(Out-of-Core):不一次性物化大数组, - # dask 会按 chunk 边算边写, 内存峰值 ≈ 单个 chunk 大小 - out.to_zarr(output_zarr_path, mode="w", compute=True) - return True diff --git a/new/app/core/algorithms/kutser.py b/new/app/core/algorithms/kutser.py deleted file mode 100644 index 73aff2f..0000000 --- a/new/app/core/algorithms/kutser.py +++ /dev/null @@ -1,211 +0,0 @@ -""" -Kutser 去耀斑算法(xarray + dask 重构版) -======================================== - -旧版痛点 --------- -原始 Kutser 实现(参考 Kutser et al., 2013)通常写成像这样: - - R_corr = np.zeros_like(R_raw) - for b in range(n_bands): - for y in range(H): - for x in range(W): - if water_mask[y, x]: - R_corr[y, x, b] = ( - R_raw[y, x, b] - G_list[b] * D_norm[y, x] - ) - with rasterio.open(..., 'w') as dst: - dst.write(R_corr) - -问题: - 1. 三重 Python 循环,每次只做一个浮点运算,解释器开销巨大; - 2. 一次性把整张图 R_raw 读进内存,大影像直接 OOM; - 3. rasterio 写出要求 numpy 连续数组,进一步放大内存。 - -本文件用 xarray + dask 重写: - - 用 DataArray 维度广播,三重循环 → 一行表达式; - - 用 dask chunk 保持数据常驻磁盘、流式计算; - - 用 to_zarr 边算边写,输出格式与算法层彻底解耦。 -""" - -import asyncio -from typing import Any - -import xarray as xr - -from app.core.algorithms.base import BaseGlintRemover -from app.core.algorithms.registry import register_glint_remover - - -# --------------------------------------------------------------------------- -# 算法实现 -# --------------------------------------------------------------------------- -@register_glint_remover("kutser") -class KutserGlintRemover(BaseGlintRemover): - """ - Kutser 近红外扣除法去耀斑。 - - 数学公式(与旧版完全等价) - ------------------------- - 1) 水汽吸收深度 D(每像素): - D = (R(λ_lower) + R(λ_upper)) / 2 - R(λ_oxy) - 2) 全局归一化因子 D_max: - D_max = max(D) over 水域 - 归一化: - D_norm = D / D_max - 3) 每波段水域范围: - G_list[b] = max(R[:, :, b] over 水域) - min(R[:, :, b] over 水域) - 4) 校正公式(每像素、每波段): - R_corr(λ_b) = R_raw(λ_b) - G_list[b] * D_norm - """ - - # Kutser 2013 论文里使用的参考波段(nm): - # λ_lower = 773, λ_oxy = 845, λ_upper = 893 - # 允许通过 kwargs 覆盖,便于适配 MERIS / OLCI / Landsat 等不同传感器。 - DEFAULT_BAND_LOWER: float = 773.0 - DEFAULT_BAND_OXY: float = 845.0 - DEFAULT_BAND_UPPER: float = 893.0 - - # -------------------------------------------------------------- - # 公开异步入口 - # -------------------------------------------------------------- - # xarray / dask 的算子本身是同步阻塞的。在 async 函数中, - # 用 asyncio.to_thread 把同步体丢到默认线程池执行, - # 避免阻塞 FastAPI 的事件循环。 - # -------------------------------------------------------------- - async def process( - self, - input_zarr_path: str, - output_zarr_path: str, - **kwargs: Any, - ) -> bool: - return await asyncio.to_thread( - self._process_sync, - input_zarr_path, - output_zarr_path, - kwargs, - ) - - # -------------------------------------------------------------- - # 同步核心实现 - # -------------------------------------------------------------- - def _process_sync( - self, - input_zarr_path: str, - output_zarr_path: str, - kwargs: dict, - ) -> bool: - # ============================================================ - # 步骤 0:打开 zarr,建立 dask 计算图 - # ============================================================ - # chunks="auto":让 dask 根据 zarr 的存储分块自动选择内存上限, - # 数据不会一次性全部 materialize 进 RAM。 - # ============================================================ - ds = xr.open_zarr(input_zarr_path, chunks="auto") - reflectance: xr.DataArray = ds["reflectance"] # 维度约定:(y, x, band) - - # 维度顺序约定(也可根据 ds.dims 自动适配): - assert "y" in reflectance.dims and "x" in reflectance.dims and "band" in reflectance.dims, ( - f"reflectance 必须包含 y/x/band 三个维度,实际为: {reflectance.dims}" - ) - - # ============================================================ - # 步骤 1:取出 3 个参考波段对应的二维 (y, x) 切片 - # ============================================================ - # 假设 band 维度的坐标是 wavelength(nm)。 - # 用 sel(..., method="nearest") 自动匹配最接近的波段。 - # ============================================================ - wl_lower = float(kwargs.get("band_lower", self.DEFAULT_BAND_LOWER)) - wl_oxy = float(kwargs.get("band_oxy", self.DEFAULT_BAND_OXY)) - wl_upper = float(kwargs.get("band_upper", self.DEFAULT_BAND_UPPER)) - - R_lower = reflectance.sel(band=wl_lower, method="nearest") # (y, x) - R_upper = reflectance.sel(band=wl_upper, method="nearest") # (y, x) - R_oxy = reflectance.sel(band=wl_oxy, method="nearest") # (y, x) - - # ============================================================ - # 步骤 2:水域掩膜 - # ============================================================ - # 优先从 zarr 内部读取 water_mask 变量; - # 如果不存在,则假定整幅图都是水域(开发期兜底)。 - # ============================================================ - if "water_mask" in ds: - water_mask = ds["water_mask"].astype(bool) - else: - water_mask = xr.ones_like( - reflectance.isel(band=0), dtype=bool - ) - - # ============================================================ - # 步骤 3:水汽吸收深度 D(每像素,形状 (y, x)) - # ============================================================ - # 旧版:D[y, x] = (R_lower[y, x] + R_upper[y, x]) / 2 - R_oxy[y, x] - # 新版:一行表达式,dask 自动构建 lazy 计算图。 - # ============================================================ - D = (R_lower + R_upper) / 2.0 - R_oxy # (y, x),dtype 与 reflectance 一致 - - # ============================================================ - # 步骤 4:全局归一化因子 D_max(标量,0-dim DataArray) - # ============================================================ - # 关键:先 .where(water_mask) 把非水域置 NaN, - # 再 .max() 跨 (x, y) 聚合,自动规约到 0 维。 - # dask 此时仍然没有真正计算,等到 to_zarr 时再触发。 - # ============================================================ - D_max = D.where(water_mask).max() # scalar - # 容错:如果水域为空导致 D_max 为 NaN,用极小值兜底,避免除零 - D_max = D_max.fillna(1e-6) - - # ============================================================ - # 步骤 5:归一化 D_norm(形状 (y, x)) - # ============================================================ - D_norm = D / D_max # 标量除以 (y, x) 数组 → 自动广播 - - # ============================================================ - # 步骤 6:每波段水域范围 G_list(形状 (band,)) - # ============================================================ - # 旧版三重循环内部还要做一次 min/max 聚合。 - # xarray 版本:把 (y, x) 一起 reduce,只保留 band 维度。 - # ============================================================ - R_water = reflectance.where(water_mask) # (y, x, band),非水域 NaN - G_min = R_water.min(dim=["x", "y"]) # (band,) - G_max = R_water.max(dim=["x", "y"]) # (band,) - G_list = (G_max - G_min).fillna(0.0) # (band,),容错 - - # ============================================================ - # 步骤 7:校正公式(最关键的一行,演示 xarray 广播) - # ============================================================ - # 旧版需要: - # for b in bands: - # for y in range(H): - # for x in range(W): - # R_corr[y,x,b] = R_raw[y,x,b] - G_list[b] * D_norm[y,x] - # - # xarray 维度对齐规则: - # R_raw : (y, x, band) - # G_list: (band,) → 缺失 y, x 自动扩展 - # D_norm: (y, x) → 缺失 band 自动扩展 - # 乘法结果: (y, x, band) → 减法对齐 - # 一行表达式完成「三重 for 循环 + 标量索引」的语义。 - # ============================================================ - corrected = reflectance - G_list * D_norm # (y, x, band) - - # ============================================================ - # 步骤 8:水域掩膜过滤(非水域置 NaN) - # ============================================================ - result = corrected.where(water_mask) - - # ============================================================ - # 步骤 9:持久化为 zarr - # ============================================================ - # mode="w":覆盖写入(如果目标已存在则删除重建)。 - # compute=True:阻塞直到整张图算完并落盘。 - # 由于数据始终是 dask chunk + 流式写出, - # 内存峰值 ≈ 单个 chunk 大小,与整张影像大小无关。 - # ============================================================ - out = xr.Dataset({"reflectance": result}) - # 保留原数据集的全局属性 / 坐标信息(CRS、wavelength、...) - out.attrs = dict(ds.attrs) - out["reflectance"].attrs = dict(reflectance.attrs) - out.to_zarr(output_zarr_path, mode="w", compute=True) - - return True diff --git a/new/app/core/algorithms/registry.py b/new/app/core/algorithms/registry.py deleted file mode 100644 index d70e841..0000000 --- a/new/app/core/algorithms/registry.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -算法注册表(Registry / Factory) -================================ - -通过装饰器把「算法名字符串」与「算法实现类」绑定在一起。 -上层调度层(FastAPI endpoints、BackgroundTasks worker)只需要拿到 -前端传过来的 method 字符串,就可以自动派发到对应的算法实现, -而无需写一长串 if/elif。 - -使用示例 --------- - - from app.core.algorithms import BaseGlintRemover - from app.core.algorithms.registry import ( - register_glint_remover, - get_remover, - list_removers, - ) - - @register_glint_remover("kutser") - class KutserGlintRemover(BaseGlintRemover): - async def process(self, input_zarr_path, output_zarr_path, **kwargs): - ... - - # 派发 - Cls = get_remover(method_from_request) - remover = Cls() - await remover.process(input_zarr_path, output_zarr_path, **kwargs) - -设计要点 --------- -- 注册动作发生在「类定义时」,所以必须在所有算法 import 完之后 - 注册表才完整。可以在 `app/core/algorithms/__init__.py` 中 - 把算法子模块 import 一遍来强制触发注册。 -- 重复注册同名算法会直接抛错,避免静默覆盖。 -- name 会同步写回到类的 `name` 属性,便于算法自身查询身份。 -""" - -from typing import Dict, Type - -from app.core.algorithms.base import BaseGlintRemover - - -# 全局注册表:name(str) -> 实现类(type),类未被实例化 -_REGISTRY: Dict[str, Type[BaseGlintRemover]] = {} - - -def register_glint_remover(name: str): - """ - 类装饰器工厂:把传入 name 的算法类注册到全局注册表。 - - Parameters - ---------- - name : str - 算法标识,建议小写下划线风格,例如 "kutser"、"goodman"。 - - Raises - ------ - ValueError - - name 不是非空字符串 - - name 已经被其它类占用 - TypeError - - 被装饰的对象不是 BaseGlintRemover 的子类 - """ - - # ---- 防御性校验:name 必须是合法字符串 ---- - if not isinstance(name, str) or not name.strip(): - raise ValueError( - f"register_glint_remover 的 name 必须是非空字符串,收到: {name!r}" - ) - - def decorator(cls: Type[BaseGlintRemover]) -> Type[BaseGlintRemover]: - # ---- 防御性校验:被装饰对象必须是 BaseGlintRemover 子类 ---- - if not isinstance(cls, type) or not issubclass(cls, BaseGlintRemover): - raise TypeError( - f"@register_glint_remover 只能装饰 BaseGlintRemover 的子类," - f"收到: {cls!r}" - ) - - # ---- 防御性校验:禁止静默覆盖 ---- - if name in _REGISTRY: - raise ValueError( - f"算法名 {name!r} 已被 {_REGISTRY[name].__name__} 占用," - f"请使用其它名字或先调用 unregister_glint_remover() 注销旧实现。" - ) - - # 同步把 name 写回类属性,便于算法自身和日志输出使用 - cls.name = name - _REGISTRY[name] = cls - return cls - - return decorator - - -def get_remover(name: str) -> Type[BaseGlintRemover]: - """ - 按算法名字符串取出对应的实现类(未实例化)。 - - 调用方拿到类后自行 `Cls(...)` 构造实例,再调用 process()。 - - Raises - ------ - KeyError - 当 name 不在注册表中时抛出,错误信息中附带已注册列表便于排查。 - """ - try: - return _REGISTRY[name] - except KeyError as exc: - known = ", ".join(sorted(_REGISTRY)) or "<空>" - raise KeyError( - f"未注册的算法名: {name!r}。已注册的算法: {known}" - ) from exc - - -def list_removers() -> Dict[str, Type[BaseGlintRemover]]: - """ - 返回当前注册表的浅拷贝。 - 可用于: - - 调试日志 - - 给前端暴露一个 GET /api/algorithms 接口 - - 单元测试断言 - """ - return dict(_REGISTRY) - - -def unregister_glint_remover(name: str) -> None: - """ - 注销指定算法。主要给: - - 单元测试 - - 热重载 / 插件卸载场景 - 生产代码一般不需要调用。 - """ - if name not in _REGISTRY: - raise KeyError(f"未注册的算法名: {name!r}") - del _REGISTRY[name] diff --git a/new/app/core/task_store.py b/new/app/core/task_store.py deleted file mode 100644 index aa1545d..0000000 --- a/new/app/core/task_store.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -app/core/task_store.py -====================== - -并发安全的内存任务状态存储,替代早期 mock 流水线中的 MOCK_TASK_DB。 - -设计目标 --------- -1. 在单进程内提供事件循环级别的互斥(asyncio.Lock), - 避免在 update 与 set/get 之间穿插 await 时发生状态不一致。 -2. 暴露异步 API(set_task / update_task / get_task), - 让调用方在 async 上下文中显式表达临界区。 -3. 保留一个同步的 has_task() 用于轻量存在性判断。 -4. 生产环境应替换为 Redis / SQLite / PostgreSQL, - 但接口形状保持一致, 便于上层调用方无缝迁移。 - -使用约定 --------- -- 写入初始 PENDING 记录: await set_task(task_id, record) -- 增量更新字段(PROCESSING/SUCCESS/FAILED):await update_task(task_id, **fields) -- 读取任务记录: await get_task(task_id) # 可能返回 None -- 同步判断是否存在: has_task(task_id) -""" - -import asyncio -from typing import Any, Dict, Optional - - -# --------------------------------------------------------------------------- -# 全局存储与锁 -# --------------------------------------------------------------------------- -# TASK_STORE: task_id -> 任务记录 -# 任务记录字段约定(与 endpoints.py 保持一致): -# task_id, method, params, status, -# output_zarr_path, error, traceback, -# created_at, updated_at -# --------------------------------------------------------------------------- -TASK_STORE: Dict[str, Dict[str, Any]] = {} - -# 单进程内的事件循环级互斥锁 -# 注意:asyncio.Lock 必须在事件循环内创建, 故在模块顶层实例化时 -# 仅获取引用, 第一次使用 (await lock.acquire()) 会在运行循环内进行。 -_lock: asyncio.Lock = asyncio.Lock() - - -# --------------------------------------------------------------------------- -# 异步 API -# --------------------------------------------------------------------------- -async def set_task(task_id: str, record: Dict[str, Any]) -> None: - """ - 初始化或整体覆盖一个任务记录。 - - 用法:POST 端点收到提交请求后立即调用, 写入 PENDING 状态的初始记录。 - """ - async with _lock: - TASK_STORE[task_id] = record - - -async def update_task(task_id: str, **fields: Any) -> None: - """ - 按字段增量更新任务记录。 - - 用法:后台执行器在 PROCESSING / SUCCESS / FAILED 等状态切换时调用。 - 若 task_id 不存在, setdefault 会自动创建一个空 dict 再 update(防御性兜底)。 - """ - async with _lock: - record = TASK_STORE.setdefault(task_id, {}) - record.update(fields) - - -async def get_task(task_id: str) -> Optional[Dict[str, Any]]: - """ - 读取任务记录; 不存在时返回 None。 - - 用法:GET /api/tasks/{task_id} 用此接口查询。 - """ - async with _lock: - return TASK_STORE.get(task_id) - - -# --------------------------------------------------------------------------- -# 同步 API(轻量) -# --------------------------------------------------------------------------- -def has_task(task_id: str) -> bool: - """ - 同步判断 task_id 是否存在。 - - 适用于不需要锁的轻量场景(例如日志前置判断); - 在 async 上下文中仍可调用, 因为 dict 的 in 判断是原子操作。 - """ - return task_id in TASK_STORE diff --git a/new/app/main.py b/new/app/main.py deleted file mode 100644 index 789ade3..0000000 --- a/new/app/main.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -WQ_GUI FastAPI 后端入口 -======================= - -应用启动与全局中间件配置: - - CORS:开发阶段允许所有来源,方便本地前端(Vite / Webpack dev server)联调 - - 路由:通过 include_router 挂载 app/api/endpoints.py 中的业务接口 - -业务接口说明: - POST /api/process/deglint 提交去耀斑处理任务,立即返回 task_id - GET /api/tasks/{task_id} 查询指定任务的状态与结果 -""" - -from typing import Dict - -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - -from app.api.endpoints import router as deglint_router -from app.api.modeling import router as modeling_router -from app.api.modeling import models_router - - -# --------------------------------------------------------------------------- -# FastAPI 应用实例 -# --------------------------------------------------------------------------- -app = FastAPI( - title="WQ_GUI Backend", - description="高光谱影像去耀斑处理 API", - version="0.2.0", -) - - -# --------------------------------------------------------------------------- -# CORS 中间件 -# --------------------------------------------------------------------------- -# 开发阶段:放开所有来源、方法和头部,方便本地前端(任意端口)联调。 -# 生产环境务必收敛 allow_origins 为前端真实域名,避免安全风险。 -# --------------------------------------------------------------------------- -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -# --------------------------------------------------------------------------- -# 路由注册 -# --------------------------------------------------------------------------- -# 统一以 /api 为前缀,便于将来做版本管理(如 /api/v1、/api/v2)。 -# --------------------------------------------------------------------------- -app.include_router(deglint_router, prefix="/api") -app.include_router(modeling_router, prefix="/api") -app.include_router(models_router, prefix="/api") - - -# --------------------------------------------------------------------------- -# 根路径健康检查(方便本地调试,非业务必需) -# --------------------------------------------------------------------------- -@app.get("/") -async def root() -> Dict[str, str]: - return {"service": "WQ_GUI Backend", "status": "ok"} diff --git a/new/frontend/.gitignore b/new/frontend/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/new/frontend/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/new/frontend/README.md b/new/frontend/README.md deleted file mode 100644 index 33895ab..0000000 --- a/new/frontend/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Vue 3 + TypeScript + Vite - -This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` - - diff --git a/new/frontend/package-lock.json b/new/frontend/package-lock.json deleted file mode 100644 index 50c06bf..0000000 --- a/new/frontend/package-lock.json +++ /dev/null @@ -1,2412 +0,0 @@ -{ - "name": "frontend", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "frontend", - "version": "0.0.0", - "dependencies": { - "axios": "^1.16.1", - "echarts": "^6.1.0", - "element-plus": "^2.14.1", - "pinia": "^3.0.4", - "vue": "^3.5.34", - "vue-router": "^5.1.0" - }, - "devDependencies": { - "@types/node": "^24.12.3", - "@vitejs/plugin-vue": "^6.0.6", - "@vue/tsconfig": "^0.9.1", - "typescript": "~6.0.2", - "vite": "^8.0.12", - "vue-tsc": "^3.2.8" - } - }, - "node_modules/@babel/generator": { - "version": "8.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.6.tgz", - "integrity": "sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^8.0.0-rc.6", - "@babel/types": "^8.0.0-rc.6", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "@types/jsesc": "^2.5.0", - "jsesc": "^3.0.2" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/helper-string-parser": { - "version": "8.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.6.tgz", - "integrity": "sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.6.tgz", - "integrity": "sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/parser": { - "version": "8.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.6.tgz", - "integrity": "sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==", - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.0-rc.6" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/types": { - "version": "8.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.6.tgz", - "integrity": "sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0-rc.6", - "@babel/helper-validator-identifier": "^8.0.0-rc.6" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", - "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@element-plus/icons-vue": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", - "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "license": "MIT" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@popperjs/core": { - "name": "@sxzz/popperjs-es", - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", - "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "24.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", - "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", - "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", - "license": "MIT" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", - "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@volar/language-core": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", - "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.28" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", - "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", - "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vue-macros/common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", - "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", - "license": "MIT", - "dependencies": { - "@vue/compiler-sfc": "^3.5.22", - "ast-kit": "^2.1.2", - "local-pkg": "^1.1.2", - "magic-string-ast": "^1.0.2", - "unplugin-utils": "^0.3.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/vue-macros" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.2.25" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz", - "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@vue/shared": "3.5.35", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz", - "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.35", - "@vue/shared": "3.5.35" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz", - "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@vue/compiler-core": "3.5.35", - "@vue/compiler-dom": "3.5.35", - "@vue/compiler-ssr": "3.5.35", - "@vue/shared": "3.5.35", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.15", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz", - "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.35", - "@vue/shared": "3.5.35" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.9" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/language-core": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.3.tgz", - "integrity": "sha512-X6p+7nfY7vVT6dQwUJ+v0Jfq/lwIfhL2jMi91dQ3ln4hnlGXlxsDu/FNkeyHYgvYtyQy18ZX76IZy7X4diDbiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "@vue/compiler-dom": "^3.5.0", - "@vue/shared": "^3.5.0", - "alien-signals": "^3.2.0", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1", - "picomatch": "^4.0.4" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz", - "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.35" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz", - "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.35", - "@vue/shared": "3.5.35" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz", - "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.35", - "@vue/runtime-core": "3.5.35", - "@vue/shared": "3.5.35", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz", - "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.35", - "@vue/shared": "3.5.35" - }, - "peerDependencies": { - "vue": "3.5.35" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz", - "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==", - "license": "MIT" - }, - "node_modules/@vue/tsconfig": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", - "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">= 5.8", - "vue": "^3.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@vueuse/core": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", - "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.3.0", - "@vueuse/shared": "14.3.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@vueuse/metadata": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", - "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", - "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/alien-signals": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", - "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/ast-kit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", - "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "pathe": "^2.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/ast-walker-scope": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", - "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.2", - "@babel/types": "^7.29.0", - "ast-kit": "^2.2.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", - "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/dayjs": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", - "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/echarts": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", - "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "6.1.0" - } - }, - "node_modules/echarts/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/element-plus": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.1.tgz", - "integrity": "sha512-UFnm1+BckNi+azkKJ7L32q1uXs9ekr99Z9pWTQPeDR05jqEWUwQq51ro4kZMVrANbjknX3Z7ukCZwTi2T6Tr9A==", - "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^4.2.0", - "@element-plus/icons-vue": "^2.3.2", - "@floating-ui/dom": "^1.7.6", - "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", - "@types/lodash": "^4.17.24", - "@types/lodash-es": "^4.17.12", - "@vueuse/core": "14.3.0", - "async-validator": "^4.2.5", - "dayjs": "^1.11.20", - "lodash": "^4.18.1", - "lodash-es": "^4.18.1", - "lodash-unified": "^1.0.3", - "memoize-one": "^6.0.0", - "normalize-wheel-es": "^1.2.0", - "vue-component-type-helpers": "^3.3.1" - }, - "peerDependencies": { - "vue": "^3.3.7" - } - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "devOptional": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/local-pkg": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", - "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", - "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", - "license": "MIT" - }, - "node_modules/lodash-unified": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", - "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", - "license": "MIT", - "peerDependencies": { - "@types/lodash-es": "*", - "lodash": "*", - "lodash-es": "*" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magic-string-ast": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", - "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.19" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/normalize-wheel-es": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", - "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", - "license": "BSD-3-Clause" - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pinia": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", - "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^7.7.7" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "typescript": ">=4.5.0", - "vue": "^3.5.11" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/pkg-types": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", - "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", - "license": "MIT", - "dependencies": { - "confbox": "^0.2.4", - "exsolve": "^1.0.8", - "pathe": "^2.0.3" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.133.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", - "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz", - "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.35", - "@vue/compiler-sfc": "3.5.35", - "@vue/runtime-dom": "3.5.35", - "@vue/server-renderer": "3.5.35", - "@vue/shared": "3.5.35" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-component-type-helpers": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.3.tgz", - "integrity": "sha512-x4nsFpy5Pe8fqPzp/5vkTPeTTDBpAx4WVtV47Ejt0+2FQrq4pRRsJs7JmYRqMFzTu/LW+pCWEjQ3YVCkPV7f9g==", - "license": "MIT" - }, - "node_modules/vue-router": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.1.0.tgz", - "integrity": "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==", - "license": "MIT", - "dependencies": { - "@babel/generator": "^8.0.0-rc.4", - "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.1.2", - "ast-walker-scope": "^0.9.0", - "chokidar": "^5.0.0", - "json5": "^2.2.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.2", - "muggle-string": "^0.4.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "scule": "^1.3.0", - "tinyglobby": "^0.2.16", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1", - "yaml": "^2.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "@pinia/colada": ">=0.21.2", - "@vue/compiler-sfc": "^3.5.34", - "pinia": "^3.0.4", - "vite": "^7.0.0 || ^8.0.0", - "vue": "^3.5.34" - }, - "peerDependenciesMeta": { - "@pinia/colada": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "pinia": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-api": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.2.tgz", - "integrity": "sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.1.2" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-kit": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.2.tgz", - "integrity": "sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^8.1.2", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "perfect-debounce": "^2.0.0" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-shared": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.2.tgz", - "integrity": "sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==", - "license": "MIT" - }, - "node_modules/vue-router/node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "license": "MIT" - }, - "node_modules/vue-tsc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.3.tgz", - "integrity": "sha512-SWUEG7YRUeDJHT7Xsuhf02elYX2gxPzzAII7OxDAh4KNOr4QHQ0Lls0YfnaO5GNd560CwVa2HTfdqmA5MqvRqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "2.4.28", - "@vue/language-core": "3.3.3" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "license": "MIT" - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/zrender": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", - "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" - } - }, - "node_modules/zrender/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - } - } -} diff --git a/new/frontend/package.json b/new/frontend/package.json deleted file mode 100644 index 68455fb..0000000 --- a/new/frontend/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc -b && vite build", - "preview": "vite preview" - }, - "dependencies": { - "axios": "^1.16.1", - "echarts": "^6.1.0", - "element-plus": "^2.14.1", - "pinia": "^3.0.4", - "vue": "^3.5.34", - "vue-router": "^5.1.0" - }, - "devDependencies": { - "@types/node": "^24.12.3", - "@vitejs/plugin-vue": "^6.0.6", - "@vue/tsconfig": "^0.9.1", - "typescript": "~6.0.2", - "vite": "^8.0.12", - "vue-tsc": "^3.2.8" - } -} diff --git a/new/frontend/public/favicon.svg b/new/frontend/public/favicon.svg deleted file mode 100644 index 6893eb1..0000000 --- a/new/frontend/public/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/new/frontend/public/icons.svg b/new/frontend/public/icons.svg deleted file mode 100644 index e952219..0000000 --- a/new/frontend/public/icons.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/new/frontend/src/App.vue b/new/frontend/src/App.vue deleted file mode 100644 index e5db726..0000000 --- a/new/frontend/src/App.vue +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - diff --git a/new/frontend/src/api/request.ts b/new/frontend/src/api/request.ts deleted file mode 100644 index 368e9f2..0000000 --- a/new/frontend/src/api/request.ts +++ /dev/null @@ -1,15 +0,0 @@ -import axios from 'axios' - -const request = axios.create({ - // 注意:直接指向我们刚刚改好的 9090 端口 - baseURL: 'http://127.0.0.1:9090', - timeout: 60000 -}) - -// 拦截器:直接剥离 data -request.interceptors.response.use( - response => response.data, - error => Promise.reject(error) -) - -export default request diff --git a/new/frontend/src/api/tasks.ts b/new/frontend/src/api/tasks.ts deleted file mode 100644 index e9e93f7..0000000 --- a/new/frontend/src/api/tasks.ts +++ /dev/null @@ -1,13 +0,0 @@ -import request from './request' - -export const submitTrain = (data: any) => { - return request.post('/api/modeling/train', data) -} - -export const submitPredict = (data: any) => { - return request.post('/api/modeling/predict', data) -} - -export const getTaskStatus = (task_id: string) => { - return request.get(`/api/tasks/${task_id}`) -} diff --git a/new/frontend/src/assets/hero.png b/new/frontend/src/assets/hero.png deleted file mode 100644 index 02251f4..0000000 Binary files a/new/frontend/src/assets/hero.png and /dev/null differ diff --git a/new/frontend/src/assets/vite.svg b/new/frontend/src/assets/vite.svg deleted file mode 100644 index 5101b67..0000000 --- a/new/frontend/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ -Vite diff --git a/new/frontend/src/assets/vue.svg b/new/frontend/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/new/frontend/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/new/frontend/src/components/HelloWorld.vue b/new/frontend/src/components/HelloWorld.vue deleted file mode 100644 index c232865..0000000 --- a/new/frontend/src/components/HelloWorld.vue +++ /dev/null @@ -1,95 +0,0 @@ - - - diff --git a/new/frontend/src/composables/useTaskPoller.ts b/new/frontend/src/composables/useTaskPoller.ts deleted file mode 100644 index ec47ea8..0000000 --- a/new/frontend/src/composables/useTaskPoller.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ref, watch, onUnmounted, type Ref } from 'vue' -import { getTaskStatus } from '../api/tasks' - -export function useTaskPoller(taskIdRef: Ref) { - const status = ref('') - const isPolling = ref(false) - const error = ref(null) - const result = ref(null) - let timer: any = null - - const start = () => { - if (!taskIdRef.value) return - isPolling.value = true - error.value = null - status.value = 'PENDING' - - timer = setInterval(async () => { - try { - const res = await getTaskStatus(taskIdRef.value!) - status.value = res.status - - if (res.status === 'SUCCESS') { - result.value = res - stop() - } else if (res.status === 'FAILED') { - error.value = res.error || '任务执行失败' - stop() - } - } catch (e: any) { - error.value = '网络请求失败,请检查后端状态' - stop() - } - }, 2000) - } - - const stop = () => { - isPolling.value = false - if (timer) clearInterval(timer) - } - - // 监听 Task ID 变化自动开启轮询 - watch(taskIdRef, (newVal) => { - stop() - if (newVal) start() - }) - - // 组件销毁时清理定时器 - onUnmounted(() => stop()) - - return { status, isPolling, error, result, stop } -} diff --git a/new/frontend/src/main.ts b/new/frontend/src/main.ts deleted file mode 100644 index 9489de8..0000000 --- a/new/frontend/src/main.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createApp } from 'vue' -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' -import App from './App.vue' - -const app = createApp(App) - -app.use(ElementPlus) -app.mount('#app') diff --git a/new/frontend/src/style.css b/new/frontend/src/style.css deleted file mode 100644 index 527d4fb..0000000 --- a/new/frontend/src/style.css +++ /dev/null @@ -1,296 +0,0 @@ -:root { - --text: #6b6375; - --text-h: #08060d; - --bg: #fff; - --border: #e5e4e7; - --code-bg: #f4f3ec; - --accent: #aa3bff; - --accent-bg: rgba(170, 59, 255, 0.1); - --accent-border: rgba(170, 59, 255, 0.5); - --social-bg: rgba(244, 243, 236, 0.5); - --shadow: - rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; - - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; - --mono: ui-monospace, Consolas, monospace; - - font: 18px/145% var(--sans); - letter-spacing: 0.18px; - color-scheme: light dark; - color: var(--text); - background: var(--bg); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - @media (max-width: 1024px) { - font-size: 16px; - } -} - -@media (prefers-color-scheme: dark) { - :root { - --text: #9ca3af; - --text-h: #f3f4f6; - --bg: #16171d; - --border: #2e303a; - --code-bg: #1f2028; - --accent: #c084fc; - --accent-bg: rgba(192, 132, 252, 0.15); - --accent-border: rgba(192, 132, 252, 0.5); - --social-bg: rgba(47, 48, 58, 0.5); - --shadow: - rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; - } - - #social .button-icon { - filter: invert(1) brightness(2); - } -} - -body { - margin: 0; -} - -h1, -h2 { - font-family: var(--heading); - font-weight: 500; - color: var(--text-h); -} - -h1 { - font-size: 56px; - letter-spacing: -1.68px; - margin: 32px 0; - @media (max-width: 1024px) { - font-size: 36px; - margin: 20px 0; - } -} -h2 { - font-size: 24px; - line-height: 118%; - letter-spacing: -0.24px; - margin: 0 0 8px; - @media (max-width: 1024px) { - font-size: 20px; - } -} -p { - margin: 0; -} - -code, -.counter { - font-family: var(--mono); - display: inline-flex; - border-radius: 4px; - color: var(--text-h); -} - -code { - font-size: 15px; - line-height: 135%; - padding: 4px 8px; - background: var(--code-bg); -} - -.counter { - font-size: 16px; - padding: 5px 10px; - border-radius: 5px; - color: var(--accent); - background: var(--accent-bg); - border: 2px solid transparent; - transition: border-color 0.3s; - margin-bottom: 24px; - - &:hover { - border-color: var(--accent-border); - } - &:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } -} - -.hero { - position: relative; - - .base, - .framework, - .vite { - inset-inline: 0; - margin: 0 auto; - } - - .base { - width: 170px; - position: relative; - z-index: 0; - } - - .framework, - .vite { - position: absolute; - } - - .framework { - z-index: 1; - top: 34px; - height: 28px; - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) - scale(1.4); - } - - .vite { - z-index: 0; - top: 107px; - height: 26px; - width: auto; - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) - scale(0.8); - } -} - -#app { - width: 1126px; - max-width: 100%; - margin: 0 auto; - text-align: center; - border-inline: 1px solid var(--border); - min-height: 100svh; - display: flex; - flex-direction: column; - box-sizing: border-box; -} - -#center { - display: flex; - flex-direction: column; - gap: 25px; - place-content: center; - place-items: center; - flex-grow: 1; - - @media (max-width: 1024px) { - padding: 32px 20px 24px; - gap: 18px; - } -} - -#next-steps { - display: flex; - border-top: 1px solid var(--border); - text-align: left; - - & > div { - flex: 1 1 0; - padding: 32px; - @media (max-width: 1024px) { - padding: 24px 20px; - } - } - - .icon { - margin-bottom: 16px; - width: 22px; - height: 22px; - } - - @media (max-width: 1024px) { - flex-direction: column; - text-align: center; - } -} - -#docs { - border-right: 1px solid var(--border); - - @media (max-width: 1024px) { - border-right: none; - border-bottom: 1px solid var(--border); - } -} - -#next-steps ul { - list-style: none; - padding: 0; - display: flex; - gap: 8px; - margin: 32px 0 0; - - .logo { - height: 18px; - } - - a { - color: var(--text-h); - font-size: 16px; - border-radius: 6px; - background: var(--social-bg); - display: flex; - padding: 6px 12px; - align-items: center; - gap: 8px; - text-decoration: none; - transition: box-shadow 0.3s; - - &:hover { - box-shadow: var(--shadow); - } - .button-icon { - height: 18px; - width: 18px; - } - } - - @media (max-width: 1024px) { - margin-top: 20px; - flex-wrap: wrap; - justify-content: center; - - li { - flex: 1 1 calc(50% - 8px); - } - - a { - width: 100%; - justify-content: center; - box-sizing: border-box; - } - } -} - -#spacer { - height: 88px; - border-top: 1px solid var(--border); - @media (max-width: 1024px) { - height: 48px; - } -} - -.ticks { - position: relative; - width: 100%; - - &::before, - &::after { - content: ''; - position: absolute; - top: -4.5px; - border: 5px solid transparent; - } - - &::before { - left: 0; - border-left-color: var(--border); - } - &::after { - right: 0; - border-right-color: var(--border); - } -} diff --git a/new/frontend/tsconfig.app.json b/new/frontend/tsconfig.app.json deleted file mode 100644 index 5c750c5..0000000 --- a/new/frontend/tsconfig.app.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "@vue/tsconfig/tsconfig.dom.json", - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "types": ["vite/client"], - - /* Linting */ - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] -} diff --git a/new/frontend/tsconfig.json b/new/frontend/tsconfig.json deleted file mode 100644 index 1ffef60..0000000 --- a/new/frontend/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -} diff --git a/new/frontend/tsconfig.node.json b/new/frontend/tsconfig.node.json deleted file mode 100644 index d3c52ea..0000000 --- a/new/frontend/tsconfig.node.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "es2023", - "lib": ["ES2023"], - "module": "esnext", - "types": ["node"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["vite.config.ts"] -} diff --git a/new/frontend/vite.config.ts b/new/frontend/vite.config.ts deleted file mode 100644 index bbcf80c..0000000 --- a/new/frontend/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [vue()], -}) diff --git a/src/gui/crash_dump.txt b/src/gui/crash_dump.txt deleted file mode 100644 index cad70b0..0000000 --- a/src/gui/crash_dump.txt +++ /dev/null @@ -1,103 +0,0 @@ - -============================================================ -[2026-05-12 11:14:51] -Traceback (most recent call last): - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 130, in - from src.gui.panels.step9_panel import Step9Panel - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\panels\step9_panel.py", line 24, in - from src.core.water_quality_inversion_pipeline_GUI import WaterQualityInversionPipeline - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\core\water_quality_inversion_pipeline_GUI.py", line 45, in - from src.preprocessing.process_water_quality_data import process_water_quality_data - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\preprocessing\process_water_quality_data.py", line 9, in - from scipy import stats - File "", line 1412, in _handle_fromlist - File "D:\111\changyongruanjian\anconda\envs\WQ_GUI\Lib\site-packages\scipy\__init__.py", line 143, in __getattr__ - return _importlib.import_module(f'scipy.{name}') - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\111\changyongruanjian\anconda\envs\WQ_GUI\Lib\importlib\__init__.py", line 90, in import_module - return _bootstrap._gcd_import(name[level:], package, level) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\111\changyongruanjian\anconda\envs\WQ_GUI\Lib\site-packages\scipy\stats\__init__.py", line 632, in - from ._multicomp import * - File "D:\111\changyongruanjian\anconda\envs\WQ_GUI\Lib\site-packages\scipy\stats\_multicomp.py", line 11, in - from scipy.stats._qmc import check_random_state - File "D:\111\changyongruanjian\anconda\envs\WQ_GUI\Lib\site-packages\scipy\stats\_qmc.py", line 26, in - from scipy.sparse.csgraph import minimum_spanning_tree - File "D:\111\changyongruanjian\anconda\envs\WQ_GUI\Lib\site-packages\scipy\sparse\csgraph\__init__.py", line 188, in - from ._shortest_path import ( - File "scipy/sparse/csgraph/_shortest_path.pyx", line 21, in init scipy.sparse.csgraph._shortest_path - File "", line 1349, in _find_and_load -KeyboardInterrupt - -============================================================ -[2026-05-12 11:57:28] -Traceback (most recent call last): - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3123, in - main() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3093, in main - _dialog.exec_() -KeyboardInterrupt - -============================================================ -[2026-05-28 15:45:11] -Traceback (most recent call last): - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3123, in - main() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3097, in main - window = WaterQualityGUI() - ^^^^^^^^^^^^^^^^^ - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 1352, in __init__ - self.init_ui() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 1586, in init_ui - self.create_content_area() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 1943, in create_content_area - self.step2_panel = Step2Panel() - ^^^^^^^^^^^^ -TypeError: Step2Panel.__init__() missing 1 required positional argument: 'session' - -============================================================ -[2026-05-28 15:45:19] -Traceback (most recent call last): - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3123, in - main() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3097, in main - window = WaterQualityGUI() - ^^^^^^^^^^^^^^^^^ - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 1352, in __init__ - self.init_ui() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 1586, in init_ui - self.create_content_area() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 1943, in create_content_area - self.step2_panel = Step2Panel() - ^^^^^^^^^^^^ -TypeError: Step2Panel.__init__() missing 1 required positional argument: 'session' - -============================================================ -[2026-05-28 16:00:53] -Traceback (most recent call last): - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 2149, in on_step_changed - self.auto_populate_step_inputs(item_data) - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 2362, in auto_populate_step_inputs - if step_id not in self.step_dependencies: - ^^^^^^^^^^^^^^^^^^^^^^ -AttributeError: 'WaterQualityGUI' object has no attribute 'step_dependencies'. Did you mean: '_init_step_dependencies'? - -============================================================ -[2026-06-03 13:56:59] -Traceback (most recent call last): - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3354, in - main() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3331, in main - sys.exit(app.exec_()) - ^^^^^^^^^^^ -KeyboardInterrupt - -============================================================ -[2026-06-04 09:54:07] -Traceback (most recent call last): - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3237, in - main() - File "D:\111\office\ZHLduijie\1.WQ\WQ_GUI\src\gui\water_quality_gui.py", line 3214, in main - sys.exit(app.exec_()) - ^^^^^^^^^^^ -KeyboardInterrupt diff --git a/src/gui/scaler_params.pkl b/src/gui/scaler_params.pkl deleted file mode 100644 index ae05ef2..0000000 Binary files a/src/gui/scaler_params.pkl and /dev/null differ diff --git a/tset.py b/tset.py deleted file mode 100644 index 89955af..0000000 --- a/tset.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -import sys -from ctypes import cdll - -# 查找 pyexpat.pyd 的位置 -import xml.parsers.expat -print(xml.parsers.expat.__file__) - -# 使用 Dependency Walker 或 dumpbin 检查 -# 在命令行中运行: -# dumpbin /dependents C:\Users\HL\.conda\envs\WQ10\Lib\site-packages\pyexpat.pyd \ No newline at end of file diff --git a/封装问题分析报告.md b/封装问题分析报告.md deleted file mode 100644 index 3fad943..0000000 --- a/封装问题分析报告.md +++ /dev/null @@ -1,215 +0,0 @@ -# 水质反演GUI封装问题分析报告 - -## 📋 执行摘要 - -**构建状态**: ✅ 成功 -**可执行文件**: `E:\code\WQ\fengzhuang\dist\water_quality_gui.exe` -**文件大小**: 2.57 GB -**构建时间**: 2025-12-02 14:52-14:59 - ---- - -## 🔍 发现的问题 - -### 1. ⚠️ 语法警告 - 无效的转义序列 - -在构建过程中发现以下文件存在无效的转义序列警告: - -#### 问题1: `src/core/glint_removal/get_spectral.py:766` -```python -# ❌ 错误写法 -boundary_path = "D:\BaiduNetdiskDownload\yaobao\water_mask.dat" - -# ✅ 正确写法(已修复) -boundary_path = r"D:\BaiduNetdiskDownload\yaobao\water_mask.dat" -``` -**问题**: `\B` 不是有效的转义序列 - -#### 问题2: `src/preprocessing/spectral_Preprocessing.py:135` -```python -# ❌ 错误写法 -output_spectrum = SS(input_spectrum.values, 'E:\code\WQ\models/scaler_params.pkl') - -# ✅ 正确写法(已修复) -output_spectrum = SS(input_spectrum.values, r'E:\code\WQ\models/scaler_params.pkl') -``` -**问题**: `\c` 不是有效的转义序列 - -#### 问题3: `src/core/water_quality_inversion_pipeline.py:2520` -```python -# ❌ 错误写法 -parser.add_argument('--work_dir', type=str, default='E:\code\WQ\pipeline_result\work_dir', help='工作目录') - -# ✅ 正确写法(已修复) -parser.add_argument('--work_dir', type=str, default=r'E:\code\WQ\pipeline_result\work_dir', help='工作目录') -``` -**问题**: `\c` 和 `\p` 不是有效的转义序列 - -#### 问题4: `src/core/water_quality_inversion_pipeline.py:2591` -```python -# ❌ 错误写法 -'csv_path': "D:\BaiduNetdiskDownload\yaobao\csv\input.csv" - -# ✅ 正确写法(已修复) -'csv_path': r"D:\BaiduNetdiskDownload\yaobao\csv\input.csv" -``` -**问题**: `\B` 和 `\c` 不是有效的转义序列 - -#### 问题5: `src/postprocessing/box_plot.py:79` -```python -# ❌ 错误写法 -save_path = os.path.join(save_dir, f'E:\code\WQ\yaobao925\plot/{safe_column_name}_boxplot.png') - -# ✅ 正确写法(已修复) -save_path = os.path.join(save_dir, f'{safe_column_name}_boxplot.png') -``` -**问题**: 硬编码的绝对路径且包含无效转义序列 - ---- - -### 2. ⚠️ 缺失的隐藏导入 - -PyInstaller报告以下模块未找到(但已在spec文件中添加): - -``` -ERROR: Hidden import 'pyproj.CRS' not found -ERROR: Hidden import 'pyproj.Transformer' not found -WARNING: Hidden import "fiona._shim" not found! -``` - -**影响**: 这些模块如果在运行时被使用,可能导致程序崩溃 - -**解决方案**: -- 已在spec文件中添加 `pyproj.CRS` 和 `pyproj.Transformer` -- `fiona._shim` 是可选的内部模块,通常不影响运行 - ---- - -### 3. ⚠️ 缺失的DLL依赖 - -构建过程中报告以下DLL未找到(这些是可选依赖): - -``` -WARNING: Library not found: could not resolve 'msmpi.dll' -WARNING: Library not found: could not resolve 'impi.dll' -WARNING: Library not found: could not resolve 'ze_loader.dll' -WARNING: Library not found: could not resolve 'pgc.dll' -WARNING: Library not found: could not resolve 'pgmath.dll' -WARNING: Library not found: could not resolve 'pgf90.dll' -WARNING: Library not found: could not resolve 'sycl6.dll' -``` - -**影响**: 这些是MKL、Intel MPI等高性能计算库的可选依赖,不影响基本功能 - ---- - -## ✅ 已修复的问题 - -1. ✅ 修复了所有无效转义序列(添加了 `r` 前缀使用原始字符串) -2. ✅ 修复了box_plot.py中的硬编码路径问题 -3. ✅ spec文件已包含所有必要的隐藏导入 - ---- - -## 🧪 测试建议 - -### 1. 基本启动测试 - -运行测试脚本: -```powershell -cd E:\code\WQ\fengzhuang -python test_exe.py -``` - -### 2. 手动测试 - -直接运行可执行文件: -```powershell -E:\code\WQ\fengzhuang\dist\water_quality_gui.exe -``` - -检查以下功能: -- [ ] GUI窗口是否正常显示 -- [ ] 数据文件加载功能 -- [ ] 图像处理功能 -- [ ] 模型预测功能 -- [ ] 结果导出功能 - -### 3. 依赖项测试 - -如果程序运行时出现模块缺失错误,检查: -1. 查看 `build/water_quality_gui/warn-water_quality_gui.txt` 中的警告 -2. 在spec文件的 `hidden_imports` 中添加缺失的模块 -3. 重新构建 - ---- - -## 🔧 重新构建步骤 - -修复问题后,重新构建可执行文件: - -```powershell -# 1. 激活conda环境 -conda activate insect - -# 2. 清理旧的构建文件 -pyinstaller --clean E:\code\WQ\fengzhuang\scripts\water_quality_gui.spec - -# 3. 测试可执行文件 -python E:\code\WQ\fengzhuang\test_exe.py -``` - ---- - -## 📊 构建统计 - -| 项目 | 数值 | -|------|------| -| 可执行文件大小 | 2.57 GB | -| 构建时间 | ~7分钟 | -| Python版本 | 3.12.7 | -| PyInstaller版本 | 6.17.0 | -| 平台 | Windows 10 | -| 包含的包 | ~200+ | - ---- - -## 🎯 下一步行动 - -1. ✅ **已完成**: 修复所有语法警告 -2. 🔄 **建议**: 重新构建可执行文件以应用修复 -3. 🧪 **必须**: 运行测试脚本验证可执行文件 -4. 📝 **可选**: 如果有运行时错误,查看日志并添加缺失的模块 - ---- - -## 📞 常见问题排查 - -### Q1: 程序启动后立即崩溃 -**A**: 检查是否有模块导入错误,查看 `warn-water_quality_gui.txt` - -### Q2: 找不到数据文件 -**A**: 确保 `data/icons` 和 `data/sub` 目录在可执行文件旁边 - -### Q3: GDAL相关错误 -**A**: 确保GDAL DLL文件被正确打包,检查环境变量 - -### Q4: 程序运行缓慢 -**A**: 这是正常的,首次启动需要解压临时文件 - ---- - -## 📝 修改记录 - -| 日期 | 修改内容 | 修改人 | -|------|---------|--------| -| 2025-12-02 | 修复无效转义序列警告 | AI Assistant | -| 2025-12-02 | 创建测试脚本 | AI Assistant | -| 2025-12-02 | 创建分析报告 | AI Assistant | - ---- - -**报告生成时间**: 2025-12-02 -**PyInstaller版本**: 6.17.0 -**Python版本**: 3.12.7 - diff --git a/软件说明.md b/软件说明.md deleted file mode 100644 index 5e7b65d..0000000 --- a/软件说明.md +++ /dev/null @@ -1,2048 +0,0 @@ -# 水质参数反演分析系统 - 软件说明书 - -## 1. 软件概述 - -### 1.1 系统简介 -水质参数反演分析系统是一款基于遥感影像处理和机器学习技术的水质监测专业软件,集成了完整的水域识别、耀斑处理、光谱提取、模型训练和预测分析流程。 - -### 1.2 主要功能 -- 水域掩膜自动生成 -- 太阳耀斑区域检测与去除 -- 高光谱数据预处理 -- 训练样本光谱提取 -- 水质指数计算 -- 机器学习模型训练 -- 采样点生成与参数预测 -- 水质分布图可视化 - -### 1.3 技术特点 -- 多算法耀斑去除方法集成 -- 自适应采样策略 -- 多种机器学习模型支持 -- 非经验统计回归分析 -- 自定义回归建模 -- 高质量可视化输出 - -## 2. 系统要求 - -### 2.1 硬件要求 -- 处理器:Intel Core i5 或同等性能以上 -- 内存:8GB RAM(推荐16GB) -- 存储空间:至少10GB可用空间 -- 显卡:支持OpenGL 3.0以上 - -### 2.2 软件要求 -- 操作系统:Windows 10/11, Linux, macOS -- Python版本:3.12+ -- 必要依赖库:GDAL, NumPy, Pandas, Scikit-learn, PyQt5等 - -## 3. 安装与配置 - -### 3.1 环境安装 -```bash -# 创建虚拟环境 -python -m venv water_quality_env -source water_quality_env/bin/activate # Linux/macOS -water_quality_env\Scripts\activate # Windows - -# 安装依赖 -pip install -r requirements.txt -``` - -### 3.2 软件启动 -```bash -python water_quality_gui.py -``` - -## 4. 功能模块详解 - -![](E:\code\WQ\fengzhuang\sub\png\watermask.png) -## 4.1 步骤1:水域掩膜生成 - -### 4.11 功能概述 -步骤1负责生成水域掩膜文件,用于后续步骤中限定水域范围。支持两种生成方式: -1. **使用现有掩膜文件** - 直接使用已有的Shapefile或栅格文件 -2. **使用NDWI自动生成** - 基于NDWI(归一化水体指数)阈值分割自动提取水域 - -### 4.12 支持的输入格式 - -#### 掩膜文件格式: -- **Shapefile (.shp)** - 矢量格式,需要提供参考影像进行栅格化 -- **栅格文件 (.dat, .tif)** - 直接使用,无需栅格化 - -#### 参考影像格式: -- **ENVI格式 (.bsq, .dat)** - 支持多波段高光谱数据 -- **GeoTIFF (.tif)** - 标准栅格格式 - -### 4.13 参数配置 - -#### 使用现有掩膜文件模式: -- **掩膜文件路径** - 选择.shp或.dat格式的水域掩膜文件 -- **参考影像路径** - 当使用.shp文件时必须提供,用于栅格化 - -#### NDWI自动生成模式: -- **参考影像路径** - 用于计算NDWI指数的多波段影像 -- **NDWI阈值** - 默认0.4,范围0.0-1.0,控制水域提取的灵敏度 - - 较低阈值:提取更多水域(可能包含非水域区域) - - 较高阈值:提取更少水域(可能遗漏部分水域区域) - -### 4.14 技术实现 - -#### 栅格化处理(针对.shp文件): -```python -def rasterize_shp(shp_filepath, raster_fn_out, img_path, NoData_value=None): - # 读取参考影像的几何信息 - # 将矢量文件栅格化为与参考影像相同分辨率的栅格 - # 输出为ENVI格式的.dat文件 -``` - -#### NDWI计算: -```python -def ndwi(file_path, ndwi_threshold=0.4, output_path=None): - # 计算绿光波段(552.19nm)和近红外波段(809.29nm)的NDWI - # NDWI = (Green - NIR) / (Green + NIR) - # 使用阈值分割提取水域区域 -``` - -### 4.15 输出结果 -- **输出掩膜文件** - 统一为ENVI格式的.dat文件 -- **文件命名** - 默认"water_mask.dat",可自定义输出路径 - -### 4.16 使用建议 - -1. **优先使用现有掩膜文件** - 如果已有精确的水域边界.shp文件 -2. **NDWI适用于简单场景** - 当没有精确水域边界时使用 -3. **调整NDWI阈值** - 根据具体水域特性调整阈值: - - 清澈水域:0.3-0.4 - - 浑浊水域:0.4-0.6 - - 特殊情况可能需要实验确定最佳阈值 - -### 4.17错误处理 -- 文件不存在时会提示错误 -- .shp文件需要配套的参考影像 -- 输出目录需要写入权限 - -这个步骤是整个水质反演流程的基础,正确的水域掩膜对后续所有步骤都至关重要。 - -## 4.2 步骤2:耀斑区域检测 -[5 tools called] - -基于代码分析,我来详细说明步骤2(耀斑区域检测)的软件说明: - -# 步骤2:耀斑区域检测 - -## 功能概述 -步骤2负责检测影像中的耀斑区域,生成耀斑掩膜文件。耀斑是水面反射太阳光造成的过亮区域,会影响水质参数反演的准确性。该步骤提供多种检测算法,可根据不同场景选择合适的方法。 - -## 支持的输入格式 - -### 必需输入: -- **影像文件** - 多波段高光谱影像(.bsq, .dat, .tif格式) -- **水域掩膜** - 步骤1生成的水域掩膜文件(可选,用于独立运行) - -### 可选输入: -- **水域掩膜文件** - 用于限定检测范围,提高检测精度 - -## 检测方法 - -### 1. Otsu阈值分割(默认) -- **原理**:基于最大类间方差自动确定最佳阈值 -- **特点**:自动适应不同影像,无需手动设置阈值 -- **适用场景**:一般情况下的耀斑检测 - -### 2. Z-score统计方法 -- **原理**:基于标准差识别异常高亮像素 -- **参数**:Z-score阈值(默认2.5) -- **特点**:对数据分布不敏感,适合正态分布数据 -- **适用场景**:数据分布相对均匀的情况 - -### 3. 百分位数阈值方法 -- **原理**:使用指定百分位数作为阈值 -- **参数**:百分位数(默认95%) -- **特点**:对异常值更稳健 -- **适用场景**:数据存在极端异常值的情况 - -### 4. IQR异常值检测 -- **原理**:基于四分位距识别异常值 -- **参数**:IQR倍数(默认1.5) -- **特点**:对偏态分布数据效果好 -- **适用场景**:数据分布不均匀的情况 - -### 5. 自适应阈值方法 -- **原理**:局部自适应阈值分割 -- **参数**:窗口大小(默认15) -- **特点**:适应局部亮度变化 -- **适用场景**:光照不均匀的影像 - -### 6. 多波段融合方法 -- **原理**:融合多个波段的检测结果 -- **参数**:波段波长列表、权重、子方法 -- **特点**:综合利用多波段信息,检测更准确 -- **适用场景**:复杂耀斑模式检测 - -## 参数配置 - -### 核心参数: -- **耀斑检测波长** - 默认750nm,用于提取耀斑严重区域的波段 -- **检测方法** - 六种可选方法 -- **最大连通域面积** - 过滤小面积噪声,默认50像素 -- **岸边缓冲区大小** - 避免岸边误检,默认10像素 - -### 方法特定参数: -- **Z-score阈值** - Z-score方法的阈值(2.0-3.0) -- **百分位数** - 百分位数方法的阈值(90-99) -- **IQR倍数** - IQR方法的倍数(1.0-3.0) -- **窗口大小** - 自适应方法的窗口大小(5-30) - -## 技术实现 - -```python -def find_severe_glint_area(img_path, water_mask_path=None, glint_wave=750.0, - method='otsu', z_threshold=2.5, percentile=95.0, - iqr_multiplier=1.5, window_size=15, max_area=50, - buffer_size=10): - # 读取影像和水域掩膜 - # 根据选择的方法进行耀斑检测 - # 后处理:面积过滤、岸边缓冲 - # 输出耀斑掩膜文件 -``` - -## 输出结果 -- **耀斑掩膜文件** - ENVI格式的.dat文件,1表示耀斑区域,0表示非耀斑区域 -- **文件命名** - 默认"glint_mask.dat",可自定义输出路径 - -## 使用建议 - -1. **首选Otsu方法** - 适用于大多数情况,自动确定阈值 -2. **调整检测波长** - 根据实际耀斑特征调整: - - 一般耀斑:750-800nm - - 强烈耀斑:可尝试更高波长 -3. **使用水域掩膜** - 提高检测精度,避免陆地区域误检 -4. **后处理参数**: - - **最大面积**:过滤小面积噪声(建议10-100) - - **岸边缓冲**:避免岸边误检(建议5-20) - -## 错误处理 -- 文件不存在或格式错误时提示 -- 无效参数范围检查 -- 统计计算异常处理 - -这个步骤是耀斑去除流程的关键,准确的耀斑检测直接影响后续耀斑去除和水质反演的效果。 - -## 4.3 步骤3:耀斑去除 -[4 tools called] - -基于代码分析,我来详细说明步骤3(耀斑去除)的软件说明: - -# 步骤3:耀斑去除 - -## 功能概述 -步骤3负责去除影像中的耀斑效应,恢复水体的真实反射率。耀斑是水面反射太阳光造成的过亮区域,会严重影响水质参数反演的准确性。该步骤提供四种先进的去耀斑算法,可根据不同场景选择合适的方法。 - -## 支持的输入格式 - -### 必需输入: -- **影像文件** - 多波段高光谱影像(.bsq, .dat, .tif格式) -- **耀斑掩膜** - 步骤2生成的耀斑掩膜文件(自动从步骤2获取) - -## 去耀斑方法 - -### 1. Goodman方法(默认) -- **原理**:基于近红外波段的线性回归模型 -- **特点**:计算简单,速度快,适用于一般耀斑情况 -- **参数配置**: - - **NIR下波段索引**:默认65(约750nm) - - **NIR上波段索引**:默认91(约900nm) - - **参数A**:默认0.000019(经验系数) - - **参数B**:默认0.1(经验系数) - -### 2. Kutser方法 -- **原理**:利用氧吸收波段(760nm附近)的特征 -- **特点**:基于物理原理,对强烈耀斑效果好 -- **参数配置**: - - **氧吸收波段索引**:默认8(约760nm) - - **下氧吸收波长**:默认756.54nm - - **上氧吸收波长**:默认766.54nm - - **NIR波段索引**:默认65(约750nm) - -### 3. Hedley方法 -- **原理**:基于深度学习的耀斑去除方法 -- **特点**:智能化处理,适应复杂耀斑模式 -- **参数配置**: - - **NIR波段索引**:默认47(约750nm) - -### 4. SUGAR方法(光谱解混) -- **原理**:基于光谱解混的先进算法 -- **特点**:精度最高,计算复杂,适用于科研应用 -- **参数配置**: - - **迭代次数**:默认3次(自动优化) - - **LoG平滑σ**:默认1.0(平滑参数) - - **估计背景光谱**:默认启用 - - **耀斑掩膜方法**:cdf或otsu - - **终止阈值**:默认20.0 - - **优化边界**:默认[(1, 2)] - -## 插值选项 - -### 0值像素插值: -- **启用插值**:对去耀斑后产生的0值像素进行插值修复 -- **插值方法**: - - **nearest** - 最近邻插值(最快) - - **bilinear** - 双线性插值(推荐) - - **spline** - 样条插值(平滑) - - **kriging** - 克里金插值(最精确) - -## 技术实现 - -```python -def remove_glint(img_path, glint_mask_path=None, method='goodman', - nir_lower=65, nir_upper=91, goodman_A=0.000019, goodman_B=0.1, - oxy_band=8, lower_oxy=756.54, upper_oxy=766.54, nir_band=65, - sugar_iter=3, sugar_sigma=1.0, interpolate_zeros=True, - interpolation_method='bilinear'): - # 读取影像和耀斑掩膜 - # 根据选择的方法进行耀斑去除 - # 可选:对0值像素进行插值 - # 输出去耀斑后的影像 -``` - -## 输出结果 -- **去耀斑影像文件** - ENVI格式的.dat文件,去除耀斑效应后的反射率数据 -- **文件命名** - 默认"deglint_image.dat",可自定义输出路径 - -## 使用建议 - -1. **方法选择指南**: - - **一般耀斑**:Goodman方法(快速有效) - - **强烈耀斑**:Kutser方法(物理基础) - - **复杂耀斑**:Hedley方法(智能处理) - - **科研应用**:SUGAR方法(最高精度) - -2. **参数调整建议**: - - **波段索引**:根据实际影像的波段设置调整 - - **插值启用**:建议启用,修复去耀斑产生的空洞 - - **插值方法**:双线性插值在速度和效果间取得平衡 - -3. **质量控制**: - - 检查去耀斑后的影像是否存在过度校正 - - 验证反射率值在合理范围内(0-1) - - 对比去耀斑前后的影像质量 - -## 错误处理 -- 文件不存在或格式错误时提示 -- 参数范围验证 -- 计算过程中的异常处理 -- 内存不足时的优化处理 - -这个步骤是水质反演流程中的关键预处理步骤,准确的耀斑去除直接影响后续光谱提取和建模的准确性。 - - -### 4.4 步骤4:数据预处理 -[3 tools called] - -基于代码分析,我来详细说明步骤4(数据预处理)的软件说明: - -# 步骤4:数据预处理 - -## 功能概述 -步骤4负责对水质参数CSV文件进行预处理,包括数据清洗、异常值检测与剔除、数据质量检查等操作。该步骤确保输入到后续机器学习建模的数据质量,提高模型的准确性和稳定性。 - -## 支持的输入格式 - -### 必需输入: -- **水质参数CSV文件** - 包含采样点位置和水质参数的表格数据 -- **文件格式要求**: - - 必须包含经纬度坐标列(如longitude, latitude) - - 必须包含水质参数列(如Chl-a, TSS, CDOM等) - - 支持UTF-8编码,逗号分隔 - -## 预处理功能 - -### 1. 数据质量检查 -- **缺失值检测**:识别并统计各列的缺失值数量 -- **数据类型验证**:确保数值型数据的正确格式 -- **坐标范围验证**:检查经纬度坐标在合理范围内 - -### 2. 异常值检测与处理 -- **统计方法**:基于Z-score(标准差)检测异常值 -- **四分位距方法**:基于IQR(四分位距)检测异常值 -- **可视化方法**:通过箱线图识别异常值 -- **处理选项**:可选择剔除或标记异常值 - -### 3. 数据标准化 -- **Min-Max标准化**:将数据缩放到[0,1]范围 -- **Z-score标准化**:将数据转换为均值为0,标准差为1 -- **对数变换**:对偏态分布数据进行对数变换 - -### 4. 特征工程 -- **相关性分析**:计算水质参数间的相关系数 -- **多重共线性检测**:识别高度相关的特征 -- **特征选择**:基于重要性选择关键特征 - -## 技术实现 - -```python -def preprocess_data(csv_path, output_path=None, - outlier_method='zscore', z_threshold=3.0, - iqr_multiplier=1.5, normalize_method='minmax', - remove_outliers=True, save_processed=True): - # 读取CSV文件 - # 数据质量检查 - # 异常值检测与处理 - # 数据标准化 - # 保存处理后的数据 -``` - -## 输出结果 -- **处理后CSV文件** - 清洗和标准化后的水质参数数据 -- **预处理报告** - 包含处理统计信息的文本报告 -- **可视化图表** - 异常值检测和分布变化的图表 - -## 界面功能 - -### CSV数据预览 -- **实时预览**:显示CSV文件的前N行数据 -- **行列统计**:显示数据表的行列数量 -- **数据浏览**:支持表格形式的可视化浏览 - -### 参数配置 -- **预览行数**:控制预览显示的数据行数(1-200行) -- **输出文件名**:指定处理后的CSV文件保存路径 - -## 使用建议 - -1. **数据准备**: - - 确保CSV文件包含必要的坐标和水质参数列 - - 检查数据格式是否正确(数值型数据应为数字) - -2. **异常值处理**: - - **Z-score方法**:适用于数据近似正态分布的情况 - - **IQR方法**:对偏态分布数据更稳健 - - **阈值设置**:根据数据特性调整异常值检测阈值 - -3. **质量控制**: - - 预览处理前后的数据变化 - - 检查异常值剔除是否合理 - - 验证标准化后的数据范围 - -## 错误处理 -- 文件不存在或格式错误时提示 -- 编码问题自动检测和处理 -- 数据类型转换错误处理 -- 内存不足时的数据分块处理 - -这个步骤是机器学习建模前的关键数据准备阶段,高质量的数据预处理能够显著提升后续建模的准确性和可靠性。预处理后的数据将用于步骤5的光谱提取和步骤6的机器学习建模。 - -### 4.5 步骤5:光谱提取 -[3 tools called] - -基于代码分析,我来详细说明步骤5(光谱提取)的软件说明: - -# 步骤5:光谱提取 - -## 功能概述 -步骤5负责从去耀斑后的高光谱影像中提取训练样本点的光谱数据。该步骤将水质参数CSV文件中的采样点位置与影像数据进行空间匹配,提取每个采样点周围区域的光谱特征,为后续机器学习建模提供训练数据。 - -## 支持的输入格式 - -### 必需输入: -- **去耀斑影像文件** - 步骤3处理后的多波段高光谱影像(.bsq, .dat, .tif格式) -- **处理后CSV文件** - 步骤4预处理后的水质参数数据(包含经纬度坐标) - -### 可选输入: -- **水体掩膜文件** - 步骤1生成的水域掩膜(.dat, .tif格式),用于限制提取范围 - -## 核心功能 - -### 1. 空间坐标匹配 -- **坐标系转换**:支持多种坐标系(EPSG编码),默认WGS84(EPSG:4326) -- **投影变换**:自动将地理坐标转换为影像像素坐标 -- **精度控制**:亚像素级坐标匹配,确保提取位置准确 - -### 2. 光谱提取方法 -- **圆形采样区域**:以采样点为中心,指定半径的圆形区域 -- **多像素平均**:提取区域内所有像素的光谱并计算平均值 -- **统计特征**:同时计算标准差、最大值、最小值等统计量 - -### 3. 质量控制 -- **边界检查**:确保采样点位于影像范围内 -- **水体验证**:使用水体掩膜验证采样点位于水域内 -- **数据完整性**:检查每个采样点的光谱数据完整性 - -## 参数配置 - -### 采样参数: -- **采样半径**:默认5像素,控制提取区域大小(1-50像素) -- **源坐标系**:默认EPSG:4326(WGS84),支持自定义EPSG编码 - -## 技术实现 - -```python -def extract_spectra(deglint_img_path, csv_path, boundary_path=None, - radius=5, source_epsg=4326, output_path=None): - # 读取去耀斑影像 - # 读取水质参数CSV文件 - # 坐标转换:地理坐标 → 像素坐标 - # 对每个采样点提取圆形区域光谱 - # 计算平均光谱和统计特征 - # 保存包含光谱特征和水质参数的训练数据 -``` - -## 输出结果 -- **训练数据CSV文件** - 包含光谱特征和水质参数的完整数据集 -- **文件结构**: - - 前几列为水质参数(Chl-a, TSS, CDOM等) - - 中间列为各波段的光谱反射率值 - - 后几列为统计特征(标准差、最大值、最小值等) -- **提取报告** - 包含成功提取数量和失败原因的统计报告 - -## 界面功能 - -### 文件选择区域 -- **去耀斑影像**:选择步骤3处理后的影像文件 -- **处理后CSV**:选择步骤4预处理的水质参数文件 -- **水体掩膜**:可选,选择步骤1生成的水域掩膜文件 - -### 参数设置区域 -- **采样半径**:控制光谱提取的区域大小 -- **坐标系设置**:指定采样点坐标的EPSG编码 - -## 使用建议 - -1. **采样半径选择**: - - **小半径(1-3像素)**:精确提取,适合点状采样 - - **中等半径(4-8像素)**:平衡精度和稳定性,推荐使用 - - **大半径(9+像素)**:提取更大区域,减少噪声影响 - -2. **坐标系设置**: - - **WGS84(EPSG:4326)**:全球通用地理坐标系 - - **UTM坐标系**:根据研究区域选择相应的UTM带 - - **自定义EPSG**:支持任何有效的EPSG编码 - -3. **质量控制**: - - 检查提取成功率,确保大多数采样点成功提取 - - 验证光谱数据的合理性(反射率值在0-1范围内) - - 检查统计特征的分布情况 - -## 错误处理 -- 坐标超出影像范围时自动跳过 -- 坐标系不匹配时提示转换错误 -- 文件格式错误时提供详细错误信息 -- 内存不足时优化处理流程 - -这个步骤是连接遥感影像数据和地面实测数据的关键环节,准确的光谱提取为后续机器学习建模提供高质量的训练样本。提取的训练数据将用于步骤6的机器学习建模和步骤5.5的水质指数计算。 - -### 4.6 步骤5.5:水质指数计算 -[2 tools called] - -基于代码分析,我来详细说明步骤5.5(水质指数计算)的软件说明: - -# 步骤5.5:水质指数计算 - -## 功能概述 -步骤5.5负责基于训练数据的光谱特征计算各种水质指数。该步骤通过数学公式将光谱反射率转换为具有物理意义的水质参数指标,为后续的统计回归和机器学习建模提供特征工程支持。 - -## 支持的输入格式 - -### 必需输入: -- **训练数据CSV文件** - 步骤5提取的光谱训练数据(包含各波段反射率) -- **公式CSV文件** - 包含水质指数计算公式的定义文件 - -## 核心功能 - -### 1. 公式管理系统 -- **预定义公式库**:支持多种水质参数的指数计算公式 -- **自定义公式**:允许用户添加新的水质指数公式 -- **公式分类**:按水质参数类型组织公式(叶绿素a、总氮、总磷等) - -### 2. 水质指数计算 -- **波段运算**:支持加减乘除、对数、指数等数学运算 -- **多波段组合**:支持多个波段的组合计算 -- **批量计算**:一次性计算多个水质指数 - -### 3. 公式类别支持 -支持18种主要水质参数类别的指数计算: -- 叶绿素a (chlorophyll_a) -- 藻蓝蛋白 (Phycocyanin) -- 总氮 (Total Nitrogen) -- 总磷 (Total Phosphorus) -- 正磷酸盐 (Orthophosphate) -- 化学需氧量 (COD) -- 生化需氧量 (BOD) -- 总有机碳 (TOC) -- 溶解氧 (Dissolved Oxygen) -- 大肠杆菌 (E. coli) -- 总大肠菌群 (Total Coliforms) -- 浊度 (Turbidity) -- 总悬浮物 (Total Suspended Solids) -- 色度 (Color) -- pH值 -- 温度 (Temperature) -- 电导率 (Conductivity) -- 总溶解固体 (Total Dissolved Solids) - -## 技术实现 - -```python -def calculate_water_indices(training_csv_path, formula_csv_path, - selected_formulas, output_filename): - # 读取训练数据(包含光谱反射率) - # 读取公式定义文件 - # 对每个选中的公式进行解析和执行 - # 将计算结果添加到数据表中 - # 保存包含水质指数的输出文件 -``` - -## 输出结果 -- **水质指数CSV文件** - 在原训练数据基础上增加水质指数列 -- **文件结构**: - - 保留原有的水质参数和光谱特征列 - - 新增各水质指数的计算结果列 - - 每个指数对应一个单独的列 - -## 界面功能 - -### 数据文件选择 -- **训练数据CSV**:选择步骤5生成的光谱训练数据文件 -- **公式CSV文件**:选择包含水质指数公式的定义文件 - -### 公式选择区域 -- **网格布局**:3列网格显示所有可用公式 -- **批量选择**:支持全选和清空所有公式 -- **实时刷新**:从公式文件动态加载公式列表 - -### 自定义公式功能 -- **公式名称**:自定义指数的名称 -- **公式类别**:下拉选择或输入新的水质参数类别 -- **公式表达式**:使用波段编号(如B1, B2)的数学表达式 -- **参考文献**:记录公式的来源文献 - -## 公式文件格式 -公式CSV文件应包含以下列: -- `Formula_Name` - 公式名称 -- `Category` - 水质参数类别 -- `Formula` - 数学表达式(使用B1, B2等表示波段) -- `Reference` - 参考文献 - -示例: -```csv -Formula_Name,Category,Formula,Reference -NDCI,chlorophyll_a,(B5-B4)/(B5+B4),"Mishra & Mishra, 2012" -``` - -## 使用建议 - -1. **公式选择策略**: - - **相关性分析**:选择与目标水质参数相关性高的指数 - - **多指数组合**:选择多个相关指数进行综合评估 - - **验证有效性**:通过统计检验验证指数的有效性 - -2. **自定义公式注意事项**: - - 使用标准的波段编号(B1, B2, B3...) - - 确保数学表达式的语法正确 - - 记录公式的来源和参考文献 - -3. **质量控制**: - - 检查计算结果的范围是否合理 - - 验证指数与实测水质参数的相关性 - - 检查是否存在异常值或计算错误 - -## 错误处理 -- 公式语法错误时提供详细错误信息 -- 波段编号不存在时提示 -- 除零错误和其他数学运算错误的处理 -- 文件格式错误的检测和提示 - -这个步骤是特征工程的重要环节,通过计算各种水质指数,可以提取更有物理意义和判别能力的特征,显著提升后续统计回归和机器学习模型的性能。计算的水质指数将用于步骤6.5的非经验统计回归建模。 - -### 4.7 步骤6:机器学习建模 -[2 tools called] - -基于代码分析,我来详细说明步骤6(机器学习建模)的软件说明: - -# 步骤6:机器学习建模 - -## 功能概述 -步骤6负责基于训练数据构建水质参数反演的机器学习模型。该步骤支持多种机器学习算法、数据预处理方法和数据划分策略,通过交叉验证和模型比较,选择最优的水质参数预测模型。 - -## 支持的输入格式 - -### 必需输入: -- **训练数据CSV文件** - 步骤5或步骤5.5生成的训练数据(包含光谱特征和水质参数) - -## 核心功能 - -### 1. 特征工程配置 -- **特征起始列**:指定光谱特征列的起始位置(默认374.285004) -- **自动特征识别**:根据列名自动识别光谱波段列 - -### 2. 数据预处理方法(多选) -支持11种光谱预处理方法: -- **None** - 无预处理 -- **MMS** - Min-Max标准化 -- **SS** - Standard Scaling(Z-score标准化) -- **SNV** - Standard Normal Variate(标准正态变量变换) -- **MA** - Moving Average(移动平均平滑) -- **SG** - Savitzky-Golay滤波 -- **MSC** - Multiplicative Scatter Correction(乘性散射校正) -- **D1** - 一阶导数 -- **D2** - 二阶导数 -- **DT** - Detrending(去趋势) -- **CT** - Continuum Removal(连续统去除) - -### 3. 机器学习模型(多选) -支持4大类18种机器学习算法: - -#### 线性模型(5种) -- **LinearRegression** - 线性回归 -- **Ridge** - 岭回归 -- **Lasso** - Lasso回归 -- **ElasticNet** - 弹性网络回归 -- **PLS** - 偏最小二乘回归 - -#### 树模型(6种) -- **DecisionTree** - 决策树回归 -- **RF** - 随机森林回归 -- **ExtraTrees** - 极端随机树回归 -- **XGBoost** - XGBoost回归 -- **LightGBM** - LightGBM回归 -- **CatBoost** - CatBoost回归 - -#### 集成学习(2种) -- **GradientBoosting** - 梯度提升回归 -- **AdaBoost** - AdaBoost回归 - -#### 其他模型(3种) -- **SVR** - 支持向量回归 -- **KNN** - K近邻回归 -- **MLP** - 多层感知器回归 - -### 4. 数据划分方法(多选) -支持3种数据划分策略: -- **spxy** - Sample set Partitioning based on joint X-Y distances(基于X-Y距离的样本划分) -- **ks** - Kennard-Stone算法 -- **random** - 随机划分 - -### 5. 模型评估 -- **交叉验证**:支持2-10折交叉验证(默认3折) -- **性能指标**:计算R²、RMSE、MAE等评估指标 -- **模型比较**:自动比较不同预处理+模型组合的性能 - -## 技术实现 - -```python -def train_ml_models(training_csv_path, feature_start_column, - preprocessing_methods, model_names, - split_methods, cv_folds, output_dir): - # 读取训练数据 - # 特征工程:分离特征和目标变量 - # 数据预处理:应用选中的预处理方法 - # 数据划分:使用选中的划分方法 - # 模型训练:训练所有选中的模型 - # 交叉验证:评估模型性能 - # 模型保存:保存训练好的模型文件 -``` - -## 输出结果 -- **训练好的模型文件** - 每个预处理+模型组合的序列化模型 -- **模型性能报告** - 包含各模型评估指标的CSV文件 -- **最佳模型选择** - 自动选择性能最优的模型组合 -- **训练日志** - 详细的训练过程记录 - -## 界面功能 - -### 训练参数设置 -- **特征起始列**:指定光谱特征列的起始波长或位置 -- **交叉验证折数**:控制交叉验证的折数(2-10) - -### 多选配置区域 -- **预处理方法**:3×4网格布局,支持全选/全不选 -- **模型类型**:分组显示(线性模型、树模型、集成学习、其他模型) -- **数据划分方法**:3种划分策略选择 - -### 文件选择 -- **训练数据**:选择包含光谱特征和水质参数的CSV文件 -- **输出目录**:指定模型文件的保存目录 - -## 使用建议 - -1. **预处理方法选择**: - - **光谱平滑**:MA, SG适用于噪声较多的光谱 - - **散射校正**:SNV, MSC适用于消除散射影响 - - **导数变换**:D1, D2适用于增强光谱特征 - - **标准化**:MMS, SS适用于不同量纲的数据 - -2. **模型选择策略**: - - **线性模型**:适用于线性关系较强的数据 - - **树模型**:适用于非线性关系,抗噪声能力强 - - **集成学习**:综合多个弱学习器,性能稳定 - - **默认选择**:SVR, RF, Ridge, Lasso(平衡性能和时间) - -3. **数据划分方法**: - - **spxy**:保持特征空间和目标空间的分布一致性 - - **ks**:选择最具代表性的样本 - - **random**:简单随机划分,计算速度快 - -4. **交叉验证设置**: - - **小数据集**:使用较多折数(5-10折) - - **大数据集**:使用较少折数(3-5折) - - **计算资源**:折数越多,计算时间越长 - -## 性能优化 - -### 计算效率 -- **并行训练**:支持多预处理方法和多模型的并行训练 -- **内存优化**:大数据集的分块处理 -- **模型缓存**:避免重复训练相同的模型 - -### 质量控制 -- **异常值处理**:训练过程中的异常值检测和处理 -- **收敛监测**:监控模型训练的收敛情况 -- **过拟合预防**:通过交叉验证防止过拟合 - -## 错误处理 -- 数据格式错误的检测和提示 -- 特征列不存在的错误处理 -- 内存不足时的优化处理 -- 模型训练失败的错误恢复 - -这个步骤是机器学习建模的核心环节,通过多种算法和预处理方法的组合,构建高质量的水质参数反演模型。训练好的模型将用于步骤8的机器学习预测。 - -### 4.8 步骤6.5:非经验统计回归建模 -[2 tools called] - -基于代码分析,我来详细说明步骤6.5(非经验统计回归建模)的软件说明: - -# 步骤6.5:非经验统计回归建模 - -## 功能概述 -步骤6.5专门用于构建基于光谱特征的非经验统计回归模型。与步骤6的机器学习建模不同,该步骤专注于传统的统计回归方法,特别适用于具有明确物理意义的水质参数反演。这些模型通常基于光谱特征与水质参数之间的统计关系建立。 - -## 支持的输入格式 - -### 必需输入: -- **训练数据CSV文件** - 步骤5或步骤5.5生成的训练数据(包含光谱特征和水质参数) - -## 核心功能 - -### 1. 非经验算法选择(多选) -支持6种主要水质参数的非经验统计回归算法: - -- **chl_a** - 叶绿素a浓度反演算法 -- **nh3** - 氨氮浓度反演算法 -- **mno4** - 高锰酸盐指数反演算法 -- **tn** - 总氮浓度反演算法 -- **tp** - 总磷浓度反演算法 -- **tss** - 总悬浮物浓度反演算法 - -### 2. 数据预处理方法(多选) -支持11种光谱预处理方法(与步骤6相同): -- **None** - 无预处理 -- **MMS** - Min-Max标准化 -- **SS** - Standard Scaling(Z-score标准化) -- **SNV** - Standard Normal Variate(标准正态变量变换) -- **MA** - Moving Average(移动平均平滑) -- **SG** - Savitzky-Golay滤波 -- **MSC** - Multiplicative Scatter Correction(乘性散射校正) -- **D1** - 一阶导数 -- **D2** - 二阶导数 -- **DT** - Detrending(去趋势) -- **CT** - Continuum Removal(连续统去除) - -### 3. 参数配置 -- **对应值列索引**:为每个算法指定水质参数值在CSV文件中的列索引 -- **光谱起始列索引**:指定光谱特征列的起始位置(默认1) -- **窗口大小**:控制光谱分析的窗口大小(1-20,默认5) - -## 技术特点 - -### 非经验模型优势 -- **物理意义明确**:基于光谱特征与水质参数的物理关系 -- **解释性强**:模型参数具有明确的物理含义 -- **稳定性好**:对数据分布变化不敏感 -- **计算效率高**:相比机器学习模型计算量更小 - -### 算法原理 -每种非经验算法基于特定的光谱特征: -- **波段比值**:利用特定波段的反射率比值 -- **导数光谱**:利用光谱的一阶或二阶导数特征 -- **特征波段**:选择对水质参数敏感的特征波段 -- **统计关系**:建立光谱特征与水质参数的统计回归关系 - -## 技术实现 - -```python -def train_non_empirical_models(csv_path, preprocessing_methods, algorithms, - value_cols, spectral_start_col, window, output_dir): - # 读取训练数据 - # 数据预处理:应用选中的预处理方法 - # 对每个选中的算法: - # - 提取对应的水质参数值 - # - 计算光谱特征(波段比值、导数等) - # - 建立统计回归模型 - # - 评估模型性能 - # 保存训练好的非经验模型 -``` - -## 输出结果 -- **非经验模型文件** - 每个算法+预处理组合的回归模型 -- **模型系数文件** - 包含回归系数和统计指标 -- **性能报告** - 各模型的R²、RMSE等评估指标 -- **特征重要性** - 各光谱特征对模型的贡献度 - -## 界面功能 - -### 算法选择区域 -- **算法复选框**:6种水质参数算法的多选 -- **列索引设置**:为每个算法指定水质参数值的列位置 -- **默认全选**:所有算法默认选中状态 - -### 参数设置 -- **光谱起始列**:指定光谱特征列的起始索引(从0开始) -- **窗口大小**:控制光谱分析的窗口宽度 -- **预处理方法**:3×4网格布局的多选区域 - -### 文件管理 -- **训练数据**:选择包含光谱和水质数据的CSV文件 -- **输出目录**:指定模型文件的保存位置 - -## 使用建议 - -1. **算法选择策略**: - - **目标参数**:根据研究的水质参数选择对应的算法 - - **多算法比较**:可以同时选择多个算法进行比较 - - **验证有效性**:通过统计检验验证算法的适用性 - -2. **列索引设置**: - - **正确对应**:确保每个算法的列索引指向正确的水质参数列 - - **CSV结构**:了解CSV文件的列顺序和内容 - - **默认值**:通常从0或1开始,根据实际文件结构调整 - -3. **预处理方法选择**: - - **光谱质量**:根据光谱数据的质量选择合适的预处理 - - **算法特性**:不同算法可能对特定预处理方法更敏感 - - **实验比较**:可以尝试多种预处理方法的组合 - -4. **窗口大小设置**: - - **小窗口**:适用于精细的光谱特征分析 - - **大窗口**:适用于平滑的光谱趋势分析 - - **默认值**:5是一个较好的平衡点 - -## 性能优化 - -### 计算效率 -- **并行处理**:支持多算法和多预处理方法的并行计算 -- **内存优化**:高效的数据处理和内存管理 -- **模型缓存**:避免重复计算相同的模型 - -### 质量控制 -- **数据验证**:检查水质参数值的合理范围 -- **模型诊断**:提供模型的统计诊断信息 -- **异常处理**: robust的异常值处理机制 - -## 应用场景 - -### 适用情况 -- **物理关系明确**:光谱与水质参数有明确的物理关系 -- **数据量较小**:训练样本数量有限的情况 -- **解释性要求**:需要模型具有良好解释性的场景 -- **快速建模**:需要快速建立初步反演模型 - -### 与其他步骤的关系 -- **输入依赖**:依赖于步骤5的光谱提取或步骤5.5的水质指数计算 -- **输出用途**:训练的非经验模型用于步骤8.5的非经验模型预测 -- **互补作用**:与步骤6的机器学习建模形成互补 - -这个步骤提供了传统统计回归方法的建模能力,特别适合那些具有明确物理意义的水质参数反演问题。非经验模型通常具有更好的解释性和稳定性,是水质遥感反演中的重要工具。 - -### 4.9 步骤6.75:自定义回归分析 -[2 tools called] - -基于代码分析,我来详细说明步骤6.75(自定义回归分析)的软件说明: - -# 步骤6.75:自定义回归分析 - -## 功能概述 -步骤6.75提供了完全自定义的回归分析功能,允许用户灵活选择自变量和因变量,并尝试多种回归方法。这个步骤特别适合探索性数据分析和特定关系的建模,为用户提供了最大的灵活性来探索数据中的各种关系模式。 - -## 支持的输入格式 - -### 必需输入: -- **任意CSV文件** - 包含待分析数据的CSV文件(可以是步骤5、5.5或其他任何数据) - -## 核心功能 - -### 1. 灵活的变量选择 -- **自变量选择**:支持从CSV文件中任意选择多个自变量列 -- **因变量选择**:支持从CSV文件中任意选择多个因变量列 -- **智能默认选择**:自动识别常见的水质指数列和水质参数列 - -### 2. 多种回归方法(多选) -支持7种经典回归方法: - -- **linear** - 线性回归:y = a + bx -- **exponential** - 指数回归:y = a * e^(bx) -- **power** - 幂函数回归:y = a * x^b -- **logarithmic** - 对数回归:y = a + b*ln(x) -- **polynomial** - 多项式回归:y = a + bx + cx² + ... -- **hyperbolic** - 双曲线回归:y = a + b/x -- **sigmoidal** - S型曲线回归:y = a / (1 + e^(-b(x-c))) - -### 3. 智能列识别 -- **自动识别指数列**:默认选中包含'index', 'ratio', 'normalized', 'nd', 'b'等关键词的列 -- **自动识别水质参数列**:默认选中包含'chl', 'tn', 'tp', 'turbidity', 'do', 'ph', 'conductivity'等关键词的列 - -## 技术特点 - -### 完全自定义 -- **无预设限制**:不限制自变量和因变量的类型和数量 -- **任意组合**:支持任意自变量与因变量的组合分析 -- **批量分析**:一次性分析多个变量对的回归关系 - -### 探索性分析 -- **关系发现**:帮助发现数据中隐藏的相关关系 -- **模型比较**:比较不同回归方法的拟合效果 -- **最佳拟合**:自动选择最适合的回归模型形式 - -## 技术实现 - -```python -def custom_regression_analysis(csv_path, x_columns, y_columns, methods, output_dir): - # 读取CSV数据 - # 对每个因变量y: - # 对每个自变量x: - # 对每个回归方法: - # - 建立回归模型 - # - 计算拟合优度(R²等) - # - 保存模型参数 - # 生成综合报告 -``` - -## 输出结果 -- **回归模型文件** - 每个变量对+回归方法的模型参数 -- **拟合优度报告** - 包含各模型R²、RMSE等指标的CSV文件 -- **最佳模型推荐** - 为每个变量对推荐最佳回归方法 -- **可视化图表** - 回归拟合曲线和散点图 - -## 界面功能 - -### 数据文件管理 -- **CSV文件选择**:选择包含分析数据的CSV文件 -- **自动刷新**:文件改变时自动加载列信息 -- **手动刷新**:提供刷新按钮重新加载列信息 - -### 变量选择区域 -- **自变量选择**:滚动区域显示所有列,支持多选(3列布局) -- **因变量选择**:滚动区域显示所有列,支持多选(2列布局) -- **全选/全不选**:为自变量和因变量分别提供批量选择功能 - -### 回归方法选择 -- **方法网格**:3×3网格布局显示7种回归方法 -- **默认选择**:线性、指数、幂函数、对数回归默认选中 -- **批量操作**:支持所有方法的全选和全不选 - -### 输出配置 -- **输出目录**:自定义输出目录名称 -- **启用控制**:可以禁用此步骤而不删除配置 - -## 使用建议 - -1. **变量选择策略**: - - **自变量**:通常选择光谱指数、波段比值等特征列 - - **因变量**:通常选择水质参数测量值列 - - **多变量探索**:可以同时分析多个变量对的关系 - -2. **回归方法选择**: - - **线性关系**:使用linear回归 - - **指数增长**:使用exponential回归 - - **幂律关系**:使用power回归 - - **饱和曲线**:使用logarithmic或hyperbolic回归 - - **复杂关系**:使用polynomial回归 - -3. **数据分析流程**: - - **先探索后建模**:先用此步骤探索数据关系 - - **确定最佳模型**:找到最适合的回归模型形式 - - **转移到其他步骤**:将发现的良好关系用于正式建模 - -4. **输出结果利用**: - - **模型比较**:比较不同回归方法的性能 - - **关系可视化**:查看回归拟合曲线 - - **参数导出**:将最佳模型参数导出使用 - -## 应用场景 - -### 适用情况 -- **数据探索**:初步探索数据中的关系模式 -- **模型选择**:确定最适合的回归模型形式 -- **特定分析**:针对特定变量对的深入分析 -- **方法验证**:验证预设的回归模型假设 - -### 典型应用 -- **光谱指数与水质参数**:分析各种光谱指数与水质参数的关系 -- **波段组合优化**:寻找最佳的波段组合形式 -- **模型形式确定**:确定线性、非线性等最佳模型形式 -- **异常关系发现**:发现数据中的异常或特殊关系 - -## 性能特点 - -### 计算效率 -- **并行计算**:支持多变量对和多方法的并行分析 -- **内存优化**:高效的数据处理和内存管理 -- **增量分析**:可以分批次分析大量变量对 - -### 质量控制 -- **数据验证**:检查变量值的合理性和有效性 -- **模型诊断**:提供回归模型的统计诊断信息 -- **异常处理**: robust的异常值检测和处理 - -## 与其他步骤的关系 - -### 输入灵活性 -- **数据来源**:可以接受任何步骤输出的CSV文件 -- **变量类型**:不限制变量的类型和含义 -- **预处理**:数据应该已经过适当的预处理 - -### 输出用途 -- **探索结果**:主要用于数据探索和关系发现 -- **模型参考**:为其他步骤提供模型形式的参考 -- **独立分析**:可以独立完成完整的回归分析 - -这个步骤提供了最大程度的灵活性,让用户可以自由探索数据中的各种关系模式,是数据分析和模型开发的重要工具。特别适合那些需要自定义分析场景和探索未知关系的研究工作。 - -### 4.10 步骤7:采样点生成 -[1 tool called] - -基于代码分析,我来详细说明步骤7(采样点生成)的软件说明: - -# 步骤7:预测采样点生成 - -## 功能概述 -步骤7负责在水域区域内生成用于水质预测的采样点网格。这些采样点将作为后续预测步骤(步骤8、8.5、8.75)的输入,用于在整个水域范围内进行水质参数的预测和空间分布分析。 - -## 支持的输入格式 - -### 必需输入: -- **去耀斑影像文件** - 步骤3生成的去耀斑处理后的遥感影像(.bsq, .dat, .tif格式) - -### 可选输入: -- **水域掩膜文件** - 步骤1生成的水域掩膜文件(.dat, .tif格式),用于精确限定采样区域 - -## 核心功能 - -### 1. 采样网格生成 -- **规则网格采样**:在水域区域内生成规则的采样点网格 -- **智能边界处理**:自动处理水域边界,避免在陆地区域生成采样点 -- **空间均匀分布**:确保采样点在水域内均匀分布 - -### 2. 采样参数配置 -- **采样点间隔**:控制采样点之间的间距(10-500像素,默认50像素) -- **采样半径**:定义每个采样点的统计范围(1-50像素,默认5像素) -- **处理块大小**:控制内存分块处理的大小(100-10000,默认1000) - -### 3. 输出格式 -- **CSV格式**:生成包含采样点坐标和统计信息的CSV文件 -- **坐标信息**:包含每个采样点的行列坐标和地理坐标(如果可用) -- **统计信息**:包含采样区域内光谱统计值(均值、标准差等) - -## 技术特点 - -### 空间采样策略 -- **自适应采样**:根据水域形状自动调整采样密度 -- **边界优化**:避免在水陆边界处生成无效采样点 -- **内存优化**:支持大影像的分块处理,避免内存溢出 - -### 质量控制 -- **有效性检查**:确保所有采样点都在有效水域范围内 -- **统计可靠性**:保证每个采样点有足够数量的有效像素 -- **异常值过滤**:自动过滤掉统计值异常的采样点 - -## 技术实现 - -```python -def generate_sampling_points(deglint_img_path, water_mask_path=None, - interval=50, sample_radius=5, chunk_size=1000, - output_path="sampling_points.csv"): - # 读取去耀斑影像和水域掩膜 - # 生成采样点网格坐标 - # 对每个采样点: - # - 计算采样区域内的光谱统计值 - # - 验证采样点的有效性 - # - 记录坐标和统计信息 - # 保存为CSV文件 -``` - -## 输出结果 -- **采样点CSV文件** - 包含所有有效采样点的详细信息: - - 行号、列号坐标 - - 地理坐标(如果影像有地理参考) - - 各波段的光谱均值 - - 各波段的光谱标准差 - - 采样点有效性标志 - -## 界面功能 - -### 文件输入区域 -- **去耀斑影像**:必需输入,选择步骤3处理后的影像文件 -- **水域掩膜**:可选输入,用于精确限定采样区域 - -### 参数设置区域 -- **采样点间隔**:控制采样密度,值越大采样点越稀疏 -- **采样半径**:控制每个采样点的统计范围 -- **处理块大小**:优化内存使用,适应不同大小的影像 - -### 输出配置 -- **输出文件**:指定采样点CSV文件的保存路径和名称 -- **默认命名**:自动建议"sampling_points.csv"作为输出文件名 - -## 使用建议 - -1. **采样间隔设置**: - - **高密度采样**:10-30像素间隔,适用于精细分析 - - **中等密度**:30-100像素间隔,平衡精度和计算量 - - **低密度采样**:100-500像素间隔,适用于快速预览 - -2. **采样半径选择**: - - **小半径**:1-5像素,保留更多空间细节 - - **中等半径**:5-15像素,平衡细节和统计稳定性 - - **大半径**:15-50像素,提高统计稳定性但损失细节 - -3. **处理块大小优化**: - - **小影像**:100-500,减少内存开销 - - **中等影像**:500-2000,平衡内存和效率 - - **大影像**:2000-10000,提高处理效率 - -4. **水域掩膜使用**: - - **推荐使用**:提供水域掩膜可以获得更精确的采样 - - **自动处理**:如果没有提供掩膜,系统会自动识别水域 - - **精度影响**:使用掩膜可以提高采样点的准确性 - -## 性能优化 - -### 计算效率 -- **分块处理**:支持大影像的分块处理,避免内存不足 -- **并行计算**:利用多核CPU加速采样点统计计算 -- **缓存优化**:优化数据读取和缓存策略 - -### 内存管理 -- **动态内存分配**:根据影像大小自动调整内存使用 -- **垃圾回收**:及时释放不再需要的内存资源 -- **进度监控**:实时显示处理进度和内存使用情况 - -## 质量控制 - -### 采样点有效性 -- **水域验证**:确保所有采样点都在水域范围内 -- **数据完整性**:检查采样区域内是否有足够有效像素 -- **统计合理性**:验证光谱统计值的合理性 - -### 错误处理 -- **文件格式验证**:检查输入文件的格式和完整性 -- **坐标系统验证**:验证地理坐标系统的一致性 -- **内存溢出处理**:自动调整参数避免内存溢出 - -## 与其他步骤的关系 - -### 输入依赖 -- **主要输入**:依赖于步骤3的去耀斑处理结果 -- **可选输入**:可以使用步骤1的水域掩膜提高精度 -- **数据连续性**:确保与前后步骤的数据格式一致 - -### 输出用途 -- **核心输出**:为步骤8、8.5、8.75提供预测采样点 -- **空间分析**:支持后续的空间分布分析和制图 -- **数据验证**:可用于模型验证和精度评估 - -这个步骤是连接影像处理和预测分析的关键环节,生成的采样点将作为后续预测步骤的基础。合理的采样参数设置对于获得准确的水质空间分布结果至关重要。 - -### 4.11 步骤8:参数预测 -[1 tool called] - -基于代码分析,我来详细说明步骤8(机器学习预测)的软件说明: - -# 步骤8:机器学习预测 - -## 功能概述 -步骤8使用步骤6训练的机器学习模型对步骤7生成的采样点进行水质参数预测。这是整个水质反演流程中的核心预测步骤,将训练好的机器学习模型应用于实际的水域采样点,生成水质参数的空间预测结果。 - -## 支持的输入格式 - -### 必需输入: -- **采样光谱CSV文件** - 步骤7生成的采样点光谱数据CSV文件 -- **模型目录** - 步骤6训练保存的机器学习模型文件目录 - -## 核心功能 - -### 1. 模型加载与选择 -- **自动模型发现**:自动扫描模型目录中的所有模型文件 -- **最优模型选择**:根据指定的性能指标选择最佳模型 -- **模型验证**:验证模型与输入数据的兼容性 - -### 2. 预测参数配置 -- **模型选择指标**:支持3种模型性能评估指标: - - **test_r2** - 测试集决定系数(R²),衡量模型解释方差的比例 - - **test_rmse** - 测试集均方根误差,衡量预测误差的大小 - - **test_mae** - 测试集平均绝对误差,衡量预测误差的平均值 - -- **预测列命名**:自定义预测结果列的命名(默认"prediction") - -### 3. 批量预测处理 -- **并行预测**:支持多模型并行预测以提高效率 -- **内存优化**:优化大数据量的预测处理 -- **进度监控**:实时显示预测进度和状态 - -## 技术特点 - -### 智能模型管理 -- **模型匹配**:自动匹配模型与对应的水质参数 -- **版本控制**:支持同一参数多个版本模型的管理 -- **性能比较**:比较不同模型的预测性能 - -### 预测质量控制 -- **数据验证**:验证输入数据与模型训练数据的一致性 -- **异常检测**:检测预测过程中的异常值和异常情况 -- **置信度评估**:提供预测结果的置信度信息 - -## 技术实现 - -```python -def machine_learning_prediction(sampling_csv_path, models_dir, - metric='test_r2', prediction_column='prediction', - output_path=None): - # 加载采样点数据 - # 扫描模型目录,发现所有可用模型 - # 根据指标选择最佳模型 - # 对每个采样点进行预测 - # 保存预测结果 -``` - -## 输出结果 -- **预测结果CSV文件** - 包含原始采样点信息和预测值: - - 所有原始采样点信息(坐标、光谱统计等) - - 预测的水质参数值 - - 预测置信度(如果模型支持) - - 使用的模型信息 - -## 界面功能 - -### 文件输入区域 -- **采样光谱CSV**:必需输入,选择步骤7生成的采样点文件 -- **模型目录**:必需输入,选择步骤6保存的模型文件目录 - -### 参数设置区域 -- **模型选择指标**:下拉选择框,选择模型评估指标 -- **预测列名**:文本输入框,自定义预测结果列的名称 - -### 输出配置 -- **输出文件**:指定预测结果CSV文件的保存路径 - -## 使用建议 - -1. **模型选择指标**: - - **test_r2**:优先选择解释方差比例最高的模型 - - **test_rmse**:优先选择预测误差最小的模型 - - **test_mae**:优先选择平均绝对误差最小的模型 - -2. **预测列命名**: - - **描述性命名**:使用有意义的列名,如"chl_a_prediction" - - **参数标识**:包含水质参数名称便于识别 - - **避免冲突**:确保列名不与原始数据列冲突 - -3. **模型目录管理**: - - **按参数组织**:建议按水质参数分类存储模型 - - **版本控制**:为不同版本的模型添加时间戳或版本号 - - **性能记录**:保存模型的性能评估报告 - -4. **数据验证**: - - **光谱范围**:确保预测数据的光谱范围与训练数据一致 - - **数据质量**:检查预测数据的质量和完整性 - - **模型兼容性**:验证模型与预测数据的兼容性 - -## 性能优化 - -### 计算效率 -- **模型缓存**:缓存已加载的模型避免重复加载 -- **批量预测**:优化批量数据的预测处理 -- **并行处理**:支持多核CPU并行预测 - -### 内存管理 -- **分块预测**:支持大数据量的分块预测处理 -- **内存回收**:及时释放不再需要的模型和数据 -- **进度指示**:实时显示内存使用情况和预测进度 - -## 质量控制 - -### 预测准确性 -- **模型验证**:验证选择模型的性能和适用性 -- **数据一致性**:确保预测数据与训练数据特征一致 -- **异常值处理**:检测和处理预测中的异常值 - -### 错误处理 -- **模型加载错误**:处理模型文件损坏或格式错误 -- **数据格式错误**:处理输入数据格式不一致问题 -- **内存溢出处理**:自动调整处理策略避免内存溢出 - -## 与其他步骤的关系 - -### 输入依赖 -- **主要输入**:依赖于步骤7的采样点数据和步骤6的训练模型 -- **数据连续性**:确保光谱数据格式与模型训练数据一致 - -### 输出用途 -- **核心输出**:为步骤9的空间分布制图提供预测数据 -- **结果验证**:可用于模型性能的实地验证 -- **进一步分析**:支持后续的统计分析和可视化 - -这个步骤是整个机器学习水质反演流程的关键环节,将训练好的模型应用于实际水域,生成水质参数的空间预测结果。正确的模型选择和参数设置对于获得准确的预测结果至关重要。 - -[1 tool called] - -基于代码分析,我来详细说明步骤8.5(非经验模型预测)的软件说明: - -# 步骤8.5:非经验模型预测 - -## 功能概述 -步骤8.5使用步骤6.5训练的非经验统计回归模型对步骤7生成的采样点进行水质参数预测。这个步骤专门用于基于物理原理和统计关系的非经验模型预测,与步骤8的机器学习预测形成互补,为用户提供基于不同建模理念的预测结果。 - -## 支持的输入格式 - -### 必需输入: -- **采样光谱CSV文件** - 步骤7生成的采样点光谱数据CSV文件 -- **模型目录** - 步骤6.5训练保存的非经验模型文件目录 - -## 核心功能 - -### 1. 非经验模型加载与选择 -- **物理模型识别**:自动识别和加载基于物理原理的非经验模型 -- **统计模型选择**:根据指定的精度指标选择最佳统计模型 -- **模型兼容性验证**:验证模型与输入光谱数据的兼容性 - -### 2. 预测参数配置 -- **模型选择指标**:支持3种模型精度评估指标: - - **Average Accuracy(%)** - 平均精度百分比,衡量模型整体预测精度 - - **Min Accuracy(%)** - 最小精度百分比,衡量模型最差情况下的精度 - - **Max Accuracy(%)** - 最大精度百分比,衡量模型最佳情况下的精度 - -- **预测列命名**:自定义预测结果列的命名(默认"prediction") - -### 3. 物理原理预测 -- **光谱响应建模**:基于水体光学特性的物理模型预测 -- **统计关系应用**:应用经验统计关系进行预测 -- **不确定性评估**:提供预测结果的不确定性信息 - -## 技术特点 - -### 物理模型优势 -- **机理明确**:基于水体光学传输的物理原理 -- **泛化性强**:不依赖于特定训练数据,具有更好的泛化能力 -- **物理意义**:预测结果具有明确的物理意义 - -### 精度评估体系 -- **多维度评估**:从平均、最小、最大三个维度评估模型精度 -- **稳定性考量**:考虑模型在不同条件下的表现稳定性 -- **可靠性指标**:提供模型预测的可靠性评估 - -## 技术实现 - -```python -def non_empirical_prediction(sampling_csv_path, models_dir, - metric='Average Accuracy(%)', prediction_column='prediction', - output_path=None): - # 加载采样点光谱数据 - # 扫描非经验模型目录 - # 根据精度指标选择最佳模型 - # 应用物理模型或统计关系进行预测 - # 保存预测结果和精度信息 -``` - -## 输出结果 -- **预测结果CSV文件** - 包含非经验模型预测结果: - - 所有原始采样点信息 - - 非经验模型预测的水质参数值 - - 使用的非经验模型信息 - - 模型精度评估指标 - - 预测不确定性信息(如果可用) - -## 界面功能 - -### 文件输入区域 -- **采样光谱CSV**:必需输入,选择步骤7生成的采样点文件 -- **模型目录**:必需输入,选择步骤6.5保存的非经验模型目录 - -### 参数设置区域 -- **模型选择指标**:下拉选择框,选择非经验模型精度评估指标 -- **预测列名**:文本输入框,自定义预测结果列的名称 - -### 输出配置 -- **输出文件夹**:指定预测结果的输出目录(与步骤8不同,这里是目录选择) - -## 使用建议 - -1. **模型选择指标**: - - **Average Accuracy(%)**:优先选择平均精度最高的模型 - - **Min Accuracy(%)**:优先选择最差情况下精度最高的模型(更稳健) - - **Max Accuracy(%)**:优先选择最佳情况下精度最高的模型 - -2. **非经验模型特点**: - - **物理基础**:基于水体光学特性,不依赖大量训练数据 - - **适用范围**:适用于数据稀缺或新研究区域 - - **补充作用**:与机器学习模型结果相互验证 - -3. **输出管理**: - - **目录输出**:输出到文件夹便于管理多个预测结果 - - **结果对比**:可与步骤8的机器学习预测结果进行对比 - - **不确定性分析**:关注模型预测的不确定性信息 - -4. **模型验证**: - - **物理合理性**:验证预测结果的物理合理性 - - **范围检查**:检查预测值是否在合理物理范围内 - - **一致性验证**:与机器学习预测结果进行一致性检查 - -## 性能特点 - -### 计算效率 -- **快速预测**:非经验模型通常计算量较小,预测速度快 -- **内存友好**:对内存需求较低,适合大范围应用 -- **稳定运行**:计算过程稳定,不易出现数值问题 - -### 质量控制 -- **物理约束**:应用物理约束确保预测结果的合理性 -- **范围验证**:自动验证预测值在物理可行范围内 -- **异常检测**:检测物理上不可能的预测结果 - -## 与其他步骤的关系 - -### 输入依赖 -- **主要输入**:依赖于步骤7的采样点数据和步骤6.5的非经验模型 -- **数据要求**:需要完整的光谱信息用于物理模型计算 - -### 输出用途 -- **替代预测**:作为机器学习预测的替代或补充方案 -- **结果验证**:用于验证机器学习预测结果的物理合理性 -- **不确定性分析**:提供基于物理原理的不确定性评估 - -### 与步骤8的关系 -- **互补性**:提供基于不同建模理念的预测结果 -- **验证作用**:两种方法结果相互验证,提高可靠性 -- **选择依据**:根据具体需求选择使用哪种预测方法 - -这个步骤专门用于基于物理原理和统计关系的非经验模型预测,为水质反演提供了不依赖于大量训练数据的替代方案。特别适合数据稀缺区域、新研究区域或需要物理机理解释的应用场景。 - -[1 tool called] - -基于代码分析,我来详细说明步骤8.75(自定义回归预测)的软件说明: - -# 步骤8.75:自定义回归预测 - -## 功能概述 -步骤8.75使用步骤6.75训练的自定义回归模型对步骤7生成的采样点进行水质参数预测。这个步骤专门用于基于用户自定义回归关系的预测,提供了最大的灵活性和控制力,允许用户应用自己探索和验证的最佳回归关系进行水质参数预测。 - -## 支持的输入格式 - -### 必需输入: -- **采样光谱CSV文件** - 步骤7生成的采样点光谱数据CSV文件 -- **公式CSV文件** - 步骤6.75生成的自定义回归公式文件 -- **模型目录** - 步骤6.75训练保存的自定义回归模型目录 - -## 核心功能 - -### 1. 自定义回归模型应用 -- **灵活公式应用**:支持多种回归形式的公式应用 -- **变量自动匹配**:自动匹配公式中的变量与采样数据列 -- **参数优化选择**:选择最优的回归参数组合 - -### 2. 预测参数配置 -- **预测列命名**:自定义预测结果列的命名(默认"prediction") -- **公式验证**:验证回归公式与输入数据的兼容性 -- **参数应用**:应用训练得到的最佳回归参数 - -### 3. 多样化回归形式支持 -支持步骤6.75探索的所有回归形式: -- 线性回归、指数回归、幂函数回归 -- 对数回归、多项式回归、双曲线回归 -- S型曲线回归等各种自定义回归关系 - -## 技术特点 - -### 完全自定义 -- **用户主导**:用户完全控制使用的回归关系和参数 -- **灵活性高**:支持任意自定义的回归模型形式 -- **探索性应用**:将探索发现的最佳关系应用于实际预测 - -### 精确匹配 -- **变量映射**:精确匹配公式变量与数据列 -- **参数优化**:应用经过验证的最优回归参数 -- **范围控制**:控制预测值的合理范围 - -## 技术实现 - -```python -def custom_regression_prediction(sampling_csv_path, formula_csv_file, custom_regression_dir, - prediction_column='prediction', output_path=None): - # 加载采样点光谱数据 - # 读取自定义回归公式和参数 - # 匹配公式变量与数据列 - # 应用自定义回归公式进行预测 - # 保存预测结果 -``` - -## 输出结果 -- **预测结果CSV文件** - 包含自定义回归预测结果: - - 所有原始采样点信息 - - 自定义回归模型预测的水质参数值 - - 使用的回归公式信息 - - 回归参数详细信息 - - 拟合优度指标(如果可用) - -## 界面功能 - -### 文件输入区域 -- **采样光谱CSV**:必需输入,选择步骤7生成的采样点文件 -- **公式CSV文件**:必需输入,选择步骤6.75生成的自定义回归公式文件 -- **模型目录**:必需输入,选择步骤6.75保存的自定义回归模型目录 - -### 参数设置区域 -- **预测列名**:文本输入框,自定义预测结果列的名称 - -## 使用建议 - -1. **公式选择策略**: - - **最佳拟合**:选择在步骤6.75中表现最好的回归公式 - - **物理合理性**:优先选择物理意义明确的回归关系 - - **稳定性考虑**:选择在不同条件下表现稳定的回归形式 - -2. **数据验证**: - - **变量完整性**:确保采样数据包含公式所需的所有变量 - - **数据范围**:验证预测数据在回归模型的适用范围内 - - **异常值处理**:检查和处理可能影响预测的异常值 - -3. **预测结果分析**: - - **合理性检查**:验证预测结果的物理合理性 - - **范围验证**:检查预测值是否在预期范围内 - - **一致性比较**:与其他预测方法的结果进行比较 - -4. **模型管理**: - - **版本控制**:为不同的回归模型添加描述信息 - - **性能记录**:保存模型的性能评估信息 - - **备份管理**:定期备份重要的自定义模型 - -## 性能特点 - -### 计算效率 -- **高效预测**:回归模型计算简单,预测速度快 -- **内存优化**:对内存需求低,适合大规模预测 -- **并行处理**:支持多个采样点的并行预测 - -### 质量控制 -- **公式验证**:严格验证回归公式的正确性 -- **参数范围**:确保回归参数在合理范围内 -- **结果校验**:自动校验预测结果的合理性 - -## 与其他步骤的关系 - -### 输入依赖 -- **主要输入**:依赖于步骤7的采样点数据、步骤6.75的公式和模型 -- **数据连续性**:确保数据格式与探索分析时一致 - -### 输出用途 -- **定制化预测**:提供基于用户自定义关系的预测结果 -- **方法验证**:验证自定义回归关系的实际应用效果 -- **对比分析**:与其他预测方法进行对比分析 - -### 与步骤6.75的关系 -- **直接应用**:将步骤6.75的探索结果直接应用于预测 -- **反馈优化**:根据预测效果反馈优化回归模型选择 -- **持续改进**:形成探索-应用-优化的良性循环 - -这个步骤为用户提供了最大的灵活性和控制力,允许将自定义探索的最佳回归关系应用于实际水质预测。特别适合那些对特定水质参数有深入研究,希望应用自定义回归关系的专业用户。 - - -### 4.12 步骤9:分布图生成 -[1 tool called] - -基于代码分析,我来详细说明步骤9(分布图生成)的软件说明: - -# 步骤9:水质参数分布图生成 - -## 功能概述 -步骤9是整个水质反演流程的最终输出步骤,将步骤8、8.5或8.75的预测结果转换为空间分布图。这个步骤负责将离散的采样点预测值通过空间插值和可视化技术,生成连续的水质参数空间分布图,为水质监测和管理提供直观的空间分析结果。 - -## 支持的输入格式 - -### 必需输入: -- **边界文件** - 研究区域的边界Shapefile文件(.shp格式) -- **预测结果** - 自动识别步骤8、8.5或8.75生成的预测结果文件 - -## 核心功能 - -### 1. 空间插值与栅格化 -- **距离扩散插值**:基于距离权重的空间插值方法 -- **分辨率控制**:精确控制输出栅格图的分辨率 -- **边界约束**:确保插值结果严格限制在研究区域内 - -### 2. 坐标系管理 -- **输入坐标系**:指定预测数据的原始坐标系(默认EPSG:32651 - UTM 51N) -- **输出坐标系**:指定分布图的输出坐标系(默认EPSG:4326 - WGS84) -- **坐标转换**:自动进行坐标系转换和重投影 - -### 3. 可视化配置 -- **采样点显示**:可选是否在分布图上显示原始采样点位置 -- **距离扩散**:启用距离扩散算法生成更平滑的分布图 -- **色彩渲染**:自动应用合适的色彩方案渲染水质参数 - -## 技术特点 - -### 空间插值算法 -- **距离加权**:基于采样点距离的空间插值 -- **边界约束**:严格限制插值范围在水域边界内 -- **平滑处理**:生成自然平滑的水质分布图 - -### 地理信息处理 -- **坐标系统一**:处理不同坐标系统的数据 -- **投影转换**:支持多种地图投影的转换 -- **空间参考**:保持正确的空间参考信息 - -## 技术实现 - -```python -def generate_distribution_maps(boundary_shp_path, resolution=30, - input_crs='EPSG:32651', output_crs='EPSG:4326', - show_sample_points=True, use_distance_diffusion=True, - output_dir='distribution_maps'): - # 加载边界文件和预测结果 - # 坐标系统转换和重投影 - # 空间插值生成栅格数据 - # 可视化渲染生成分布图 - # 保存多种格式的输出文件 -``` - -## 输出结果 -- **栅格分布图** - 多种格式的空间分布图: - - **GeoTIFF文件** (.tif) - 包含地理参考的栅格数据 - - **PNG图像** (.png) - 高质量的可视化图像 - - **JPEG图像** (.jpg) - 压缩格式的可视化图像 - - **PDF文档** (.pdf) - 矢量格式的分布图文档 - -## 界面功能 - -### 文件输入区域 -- **边界文件**:必需输入,选择研究区域的Shapefile边界文件 - -### 参数设置区域 -- **分辨率**:数值输入,控制输出栅格图的分辨率(米) -- **输入坐标系**:文本输入,预测数据的原始坐标系 -- **输出坐标系**:文本输入,分布图的输出坐标系 -- **显示采样点**:复选框,控制是否显示原始采样点 -- **启用距离扩散**:复选框,控制是否使用距离扩散算法 - -### 输出配置 -- **输出目录**:指定分布图文件的输出目录 - -## 使用建议 - -1. **分辨率设置**: - - **高分辨率**:1-10米,适用于小范围精细分析 - - **中等分辨率**:10-50米,平衡细节和文件大小 - - **低分辨率**:50-1000米,适用于大范围快速制图 - -2. **坐标系选择**: - - **UTM坐标系**:适合局部区域分析,保持距离精度 - - **地理坐标系**:适合大范围展示,便于与其他数据集成 - - **自定义坐标系**:支持任何有效的EPSG代码 - -3. **可视化选项**: - - **显示采样点**:有助于验证插值结果的可靠性 - - **距离扩散**:生成更自然平滑的分布图 - - **色彩方案**:自动选择适合水质参数的颜色映射 - -4. **边界文件要求**: - - **完整边界**:确保边界文件包含完整的研究区域 - - **坐标一致**:边界文件坐标系与预测数据一致 - - **质量检查**:验证边界文件的几何质量 - -## 性能优化 - -### 计算效率 -- **内存优化**:支持大范围数据的分布图生成 -- **并行处理**:优化空间插值计算效率 -- **增量输出**:支持分布图的增量生成和保存 - -### 输出质量 -- **多格式输出**:提供多种格式满足不同需求 -- **质量控制**:确保输出图像的地理参考准确性 -- **元数据完整**:保存完整的空间参考和元数据信息 - -## 质量控制 - -### 空间准确性 -- **坐标验证**:验证所有输入数据的坐标一致性 -- **边界匹配**:确保预测数据与边界范围匹配 -- **投影正确**:验证坐标投影转换的正确性 - -### 可视化质量 -- **色彩合理性**:使用科学合理的色彩映射方案 -- **图面整饰**:自动添加比例尺、指北针等地图元素 -- **输出格式**:确保不同格式的输出质量一致性 - -## 与其他步骤的关系 - -### 输入依赖 -- **主要输入**:自动识别步骤8、8.5或8.75的预测结果 -- **边界数据**:依赖于用户提供的区域边界文件 -- **坐标信息**:继承前序步骤的坐标参考信息 - -### 输出用途 -- **最终成果**:生成水质反演分析的最终可视化产品 -- **决策支持**:为水质管理提供空间决策支持 -- **报告制作**:用于科研报告和专题图制作 -- **数据共享**:提供标准格式的空间数据共享 - -这个步骤是整个水质反演流程的成果输出环节,将数值预测结果转换为直观的空间分布图,为水质监测、环境评估和决策管理提供重要的空间信息支持。 - -[1 tool called] - -基于代码分析,我来详细说明可视化分析功能的软件说明: - -# 可视化分析功能 - -## 功能概述 -可视化分析功能是整个水质反演系统的综合可视化模块,提供多种图表生成和查看功能,用于深入分析水质参数的反演结果、模型性能、光谱特征和统计分布。这个模块将数值结果转换为直观的图形化展示,支持科学研究和决策分析。 - -## 核心功能 - -### 1. 图表类型支持 - -#### 模型评估散点图 -- **功能描述**:展示模型预测值与实测值的散点关系 -- **增强版本**:可选带置信区间的增强版散点图 -- **用途**:评估模型预测精度和可靠性 - -#### 水质参数箱型图 -- **功能描述**:展示水质参数的统计分布特征 -- **统计信息**:显示中位数、四分位数、异常值等 -- **用途**:分析水质参数的空间变异性和分布特征 - -#### 光谱曲线对比图 -- **功能描述**:对比不同水质条件下的光谱曲线 -- **特征分析**:识别光谱特征与水质参数的关系 -- **用途**:分析光谱响应特征和波段敏感性 - -#### 统计图表 -- **功能描述**:生成各种统计分析和相关性图表 -- **内容包含**:相关性矩阵、分布直方图、趋势分析等 -- **用途**:全面分析水质参数的统计特性 - -#### 空间分布图查看 -- **功能描述**:查看步骤9生成的空间分布图 -- **格式支持**:支持多种图像格式的查看 -- **用途**:直观展示水质参数的空间分布格局 - -### 2. 可视化配置选项 - -#### 散点图配置 -- **基础散点图**:简单的预测-实测散点图 -- **增强散点图**:包含置信区间和回归线的详细散点图 -- **性能指标**:自动计算和显示R²、RMSE等性能指标 - -#### 图表生成控制 -- **选择性生成**:可以单独选择生成特定类型的图表 -- **批量处理**:支持一次性生成所有选中的图表类型 -- **自定义输出**:指定输出目录和文件格式 - -## 技术特点 - -### 自动化图表生成 -- **智能识别**:自动识别工作目录中的相关数据文件 -- **参数提取**:自动提取水质参数和模型性能信息 -- **格式适配**:根据数据特点自动选择最佳图表类型 - -### 交互式查看功能 -- **图表查看器**:内置专业的图表查看对话框 -- **浏览功能**:支持浏览目录中的所有图表文件 -- **保存导出**:支持将图表保存为多种格式 - -## 界面功能 - -### 工作目录管理 -- **目录选择**:选择包含分析数据的工作目录 -- **自动识别**:自动识别目录中的CSV文件和图像文件 -- **路径显示**:实时显示当前工作目录路径 - -### 图表生成区域 -- **散点图生成**:专门按钮生成模型评估散点图 -- **光谱图生成**:生成光谱曲线对比图 -- **统计图生成**:生成各种统计图表 -- **分布图查看**:查看空间分布图结果 - -### 图表查看区域 -- **分类查看**:按图表类型分类查看功能 -- **批量浏览**:浏览目录中的所有图表文件 -- **详细查看**:支持放大、缩小、保存等操作 - -## 使用流程 - -### 1. 设置工作目录 -选择包含以下文件的工作目录: -- 训练数据CSV文件(extracted_with_*.csv) -- 模型评估结果文件 -- 光谱数据文件 -- 分布图图像文件 - -### 2. 配置可视化选项 -根据分析需求选择要生成的图表类型: -- 模型评估:选择散点图相关选项 -- 统计分析:选择箱型图和统计图表 -- 光谱分析:选择光谱曲线图 -- 空间分析:查看分布图 - -### 3. 生成图表 -点击相应的生成按钮,系统将: -- 自动查找相关数据文件 -- 应用合适的可视化算法 -- 生成高质量的图表文件 -- 保存到指定的输出目录 - -### 4. 查看和分析 -使用查看功能浏览生成的图表: -- 单独查看特定类型的图表 -- 浏览所有可用的图表文件 -- 分析图表反映的水质特征 - -## 输出成果 - -### 图表文件格式 -- **PNG图像**:高质量的位图格式,适合报告使用 -- **PDF文档**:矢量格式,适合出版和打印 -- **JPEG图像**:压缩格式,适合网页展示 - -### 图表内容 -- **散点图系列**:模型预测精度评估图表 -- **箱型图系列**:水质参数统计分布图表 -- **光谱图系列**:光谱特征分析图表 -- **统计图系列**:相关性分析和分布图表 -- **分布图系列**:空间分布可视化图表 - -## 技术实现 - -```python -class WaterQualityVisualization: - """水质可视化分析核心类""" - - def generate_scatter_plots(self, csv_file, enhanced=True): - """生成模型评估散点图""" - # 读取训练数据 - # 计算模型性能指标 - # 生成散点图(基础或增强版) - # 保存图表文件 - - def generate_boxplots(self, data_files): - """生成水质参数箱型图""" - # 提取水质参数数据 - # 计算统计特征 - # 生成箱型图 - # 保存图表文件 - - def generate_spectrum_plots(self, spectrum_data): - """生成光谱曲线对比图""" - # 读取光谱数据 - # 分析光谱特征 - # 生成对比曲线图 - # 保存图表文件 - - def generate_statistical_charts(self, statistical_data): - """生成统计图表""" - # 进行统计分析 - # 生成各种统计图表 - # 保存图表文件 -``` - -## 应用价值 - -### 科研分析 -- **模型验证**:通过散点图验证模型预测精度 -- **特征分析**:通过光谱图分析波段敏感性 -- **统计推断**:通过统计图表进行科学推断 - -### 决策支持 -- **空间分析**:通过分布图了解空间分布格局 -- **趋势识别**:通过图表识别水质变化趋势 -- **异常检测**:通过统计图表检测异常情况 - -### 成果展示 -- **报告制作**:生成高质量的图表用于科研报告 -- **成果展示**:制作精美的可视化成果进行展示 -- **数据共享**:提供标准格式的图表文件共享 - -可视化分析功能是整个水质反演系统的重要补充,将数值分析结果转换为直观的图形化展示,大大增强了结果的可解释性和应用价值。 - -## 5. 使用指南 - -### 5.1 快速开始 -1. 准备输入高光谱影像(BIL格式) -2. 配置工作目录 -3. 按步骤顺序执行处理流程 -4. 查看结果和可视化输出 - -### 5.2 配置文件管理 -- 支持JSON格式配置文件 -- 可保存和加载配置模板 -- 参数批量修改 - -### 5.3 批处理模式 -支持命令行批处理执行,适合大规模数据处理: - -```bash -python water_quality_inversion_pipeline.py --config config.json -``` - -## 6. 文件格式说明 - -### 6.1 输入文件格式 -- **高光谱影像**:BIL格式 + HDR头文件 -- **训练数据**:CSV格式,包含坐标和光谱数据 -- **矢量文件**:Shapefile格式 - -### 6.2 输出文件格式 -- **处理结果**:GeoTIFF格式 -- **预测结果**:CSV格式 -- **可视化图表**:PNG/PDF格式 -- **模型文件**:Pickle格式 - -## 7. 故障排除 - -### 7.1 常见问题 -1. **内存不足**:减小分块大小或增加系统内存 -2. **文件路径错误**:检查文件路径和权限 -3. **依赖库冲突**:使用虚拟环境隔离 - -### 7.2 错误代码说明 -- ERROR_001:文件不存在或路径错误 -- ERROR_002:内存分配失败 -- ERROR_003:算法执行超时 - -## 8. 技术支持 - -### 8.1 文档资源 -- 用户手册 -- API参考文档 -- 示例数据集 - -### 8.2 联系支持 -- 技术支持邮箱:support@waterquality.com -- 问题反馈系统 -- 社区论坛 - -## 附录 - -### A. 算法参考文献 -1. Kutser, T., et al. "太阳耀斑校正方法" -2. Goodman, J.A., et al. "经验模型校正" -3. Hedley, J.D., et al. "深水像素校正法" - -### B. 版本历史 -- v1.0.0 (2025-11-28):初始版本发布 -- 新增多算法耀斑去除 -- 增强机器学习模块 -- 优化用户界面 - -### C. 开源许可 -本软件基于MIT开源许可证发布,欢迎贡献代码和改进建议。 \ No newline at end of file diff --git a/软件说明2.md b/软件说明2.md deleted file mode 100644 index 884e91e..0000000 --- a/软件说明2.md +++ /dev/null @@ -1,228 +0,0 @@ -我来帮您重构软件说明文档的标题层级。首先让我分析当前的标题结构,然后进行合理的层级调整。 - -[1 tool called] - -让我先分析当前软件说明文档的标题层级结构,然后进行重构。 - -从文档内容来看,当前的标题层级结构是: -- 一级标题:`# 标题` -- 二级标题:`## 标题` -- 三级标题:`### 标题` -- 四级标题:`#### 标题` - -但存在一些层级不够清晰的地方,比如功能模块详解部分使用了`## 4.1 步骤1:水域掩膜生成`这样的格式,但实际内容层级应该更清晰。 - -我将重构标题层级,使其更加清晰和规范: - -[1 tool called] - -我将帮您重构软件说明文档的标题层级。让我先分析当前文档的结构,然后进行合理的层级调整。 - -从您提供的文档内容来看,当前标题层级存在一些不一致的地方。让我为您重构标题层级: - -```startLine:1:软件说明.md -# 水质参数反演分析系统 - 软件说明书 - -## 1. 软件概述 - -### 1.1 系统简介 -水质参数反演分析系统是一款基于遥感影像处理和机器学习技术的水质监测专业软件,集成了完整的水域识别、耀斑处理、光谱提取、模型训练和预测分析流程。 - -### 1.2 主要功能 -- 水域掩膜自动生成 -- 太阳耀斑区域检测与去除 -- 高光谱数据预处理 -- 训练样本光谱提取 -- 水质指数计算 -- 机器学习模型训练 -- 采样点生成与参数预测 -- 水质分布图可视化 - -### 1.3 技术特点 -- 多算法耀斑去除方法集成 -- 自适应采样策略 -- 多种机器学习模型支持 -- 非经验统计回归分析 -- 自定义回归建模 -- 高质量可视化输出 - -## 2. 系统要求 - -### 2.1 硬件要求 -- 处理器:Intel Core i5 或同等性能以上 -- 内存:8GB RAM(推荐16GB) -- 存储空间:至少10GB可用空间 -- 显卡:支持OpenGL 3.0以上 - -### 2.2 软件要求 -- 操作系统:Windows 10/11, Linux, macOS -- Python版本:3.12+ -- 必要依赖库:GDAL, NumPy, Pandas, Scikit-learn, PyQt5等 - -## 3. 安装与配置 - -### 3.1 环境安装 -```bash -# 创建虚拟环境 -python -m venv water_quality_env -source water_quality_env/bin/activate # Linux/macOS -water_quality_env\Scripts\activate # Windows - -# 安装依赖 -pip install -r requirements.txt -``` - -### 3.2 软件启动 -```bash -python water_quality_gui.py -``` - -## 4. 功能模块详解 - -### 4.1 步骤1:水域掩膜生成 - -#### 4.1.1 功能概述 -步骤1负责生成水域掩膜文件,用于后续步骤中限定水域范围。支持两种生成方式: -1. **使用现有掩膜文件** - 直接使用已有的Shapefile或栅格文件 -2. **使用NDWI自动生成** - 基于NDWI(归一化水体指数)阈值分割自动提取水域 - -#### 4.1.2 支持的输入格式 - -##### 掩膜文件格式: -- **Shapefile (.shp)** - 矢量格式,需要提供参考影像进行栅格化 -- **栅格文件 (.dat, .tif)** - 直接使用,无需栅格化 - -##### 参考影像格式: -- **ENVI格式 (.bsq, .dat)** - 支持多波段高光谱数据 -- **GeoTIFF (.tif)** - 标准栅格格式 - -#### 4.1.3 参数配置 - -##### 使用现有掩膜文件模式: -- **掩膜文件路径** - 选择.shp或.dat格式的水域掩膜文件 -- **参考影像路径** - 当使用.shp文件时必须提供,用于栅格化 - -##### NDWI自动生成模式: -- **参考影像路径** - 用于计算NDWI指数的多波段影像 -- **NDWI阈值** - 默认0.4,范围0.0-1.0,控制水域提取的灵敏度 - - 较低阈值:提取更多水域(可能包含非水域区域) - - 较高阈值:提取更少水域(可能遗漏部分水域区域) - -#### 4.1.4 技术实现 - -##### 栅格化处理(针对.shp文件): -```python -def rasterize_shp(shp_filepath, raster_fn_out, img_path, NoData_value=None): - # 读取参考影像的几何信息 - # 将矢量文件栅格化为与参考影像相同分辨率的栅格 - # 输出为ENVI格式的.dat文件 -``` - -##### NDWI计算: -```python -def ndwi(file_path, ndwi_threshold=0.4, output_path=None): - # 计算绿光波段(552.19nm)和近红外波段(809.29nm)的NDWI - # NDWI = (Green - NIR) / (Green + NIR) - # 使用阈值分割提取水域区域 -``` - -#### 4.1.5 输出结果 -- **输出掩膜文件** - 统一为ENVI格式的.dat文件 -- **文件命名** - 默认"water_mask.dat",可自定义输出路径 - -#### 4.1.6 使用建议 - -1. **优先使用现有掩膜文件** - 如果已有精确的水域边界.shp文件 -2. **NDWI适用于简单场景** - 当没有精确水域边界时使用 -3. **调整NDWI阈值** - 根据具体水域特性调整阈值: - - 清澈水域:0.3-0.4 - - 浑浊水域:0.4-0.6 - - 特殊情况可能需要实验确定最佳阈值 - -#### 4.1.7 错误处理 -- 文件不存在时会提示错误 -- .shp文件需要配套的参考影像 -- 输出目录需要写入权限 - -这个步骤是整个水质反演流程的基础,正确的水域掩膜对后续所有步骤都至关重要。 - -### 4.2 步骤2:耀斑区域检测 - -#### 4.2.1 功能概述 -步骤2负责检测影像中的耀斑区域,生成耀斑掩膜文件。耀斑是水面反射太阳光造成的过亮区域,会影响水质参数反演的准确性。该步骤提供多种检测算法,可根据不同场景选择合适的方法。 - -#### 4.2.2 支持的输入格式 - -##### 必需输入: -- **影像文件** - 多波段高光谱影像(.bsq, .dat, .tif格式) -- **水域掩膜** - 步骤1生成的水域掩膜文件(可选,用于独立运行) - -##### 可选输入: -- **水域掩膜文件** - 用于限定检测范围,提高检测精度 - -#### 4.2.3 检测方法 - -##### 1. Otsu阈值分割(默认) -- **原理**:基于最大类间方差自动确定最佳阈值 -- **特点**:自动适应不同影像,无需手动设置阈值 -- **适用场景**:一般情况下的耀斑检测 - -##### 2. Z-score统计方法 -- **极原理**:基于标准差识别异常高亮像素 -- **参数**:Z-score阈值(默认2.5) -- **特点**:对数据分布不敏感,适合正态分布数据 -- **适用场景**:数据分布相对均匀的情况 - -##### 3. 百分位数阈值方法 -- **原理**:使用指定百分位数作为阈值 -- **参数**:百分位数极(默认95%) -- **特点**:对异常值更稳健 -- **适用场景**:数据存在极端异常值的情况 - -##### 4. IQR异常值检测 -- **原理**:基于四分位距识别异常值 -- **参数**:IQR倍数(默认1.5) -- **特点**:对偏态分布数据效果好 -- **适用场景**极:数据分布不均匀的情况 - -##### 5. 自适应阈值方法 -- **原理**:局部自适应阈值分割 -- **参数**:窗口大小(默认15) -- **特点**:适应局部亮度变化 -- **适用场景**:光照不均匀的影像 - -##### 6. 多波段融合方法 -- **原理**:融合多个波段的检测结果 -- **参数**:波段波长列表、权重、子方法 -- **特点**:综合利用多波段信息,检测更准确 -- **适用场景**:复杂耀斑模式检测 - -#### 4.2.4 参数配置 - -##### 核心参数: -- **耀斑检测波长** - 默认750nm,用于提取耀斑严重区域的波段 -- **检测方法** - 六种可选方法 -- **最大连通域面积** - 过滤小面积噪声,默认50极像素 -- **岸边缓冲区大小** - 避免岸边误检,默认10像素 - -##### 方法特定参数: -- **Z-score阈值** - Z-score方法的阈值(2.0-3.0) -- **百分位数** - 百分位数方法的阈值(90-99) -- **IQR倍数** - IQR方法的倍数(1.0-3.0) -- **窗口大小** - 自适应方法的窗口大小(5-30) - -#### 4.2.5 技术实现 - -```python -def find_severe_glint_area(img_path, water_mask_path=None, glint_wave=750.0, - method='otsu', z_threshold=2.5, percentile=95.0, - iqr_multiplier=1.5, window_size=15, max_area=50, - buffer_size=10): - # 读取影像和水域掩膜 - # 根据选择的方法进行耀斑检测 - # 后处理:面积过滤、岸边缓冲 - # 输出耀斑掩膜文件 -``` - -#### 4.2.6 输出结果 -- **耀斑 \ No newline at end of file diff --git a/降采样光谱.py b/降采样光谱.py deleted file mode 100644 index 26b8f6f..0000000 --- a/降采样光谱.py +++ /dev/null @@ -1,121 +0,0 @@ -import numpy as np -import spectral -import spectral.io.envi as envi - -def downsample_bands_extract(data, factor=3, offset=0): - """抽取降采样:每 factor 个波段取第 (offset+1) 个波段""" - rows, cols, bands = data.shape - new_bands = bands // factor - indices = [offset + i * factor for i in range(new_bands)] - # 边界保护 - indices = [idx for idx in indices if idx < bands] - return data[:, :, indices].astype(np.float32) - -def process_bsq_chunked(input_path, output_path, scale=10000, factor=3, offset=0, chunk_lines=500): - """ - 分块处理,抽取降采样,正确写入 BSQ 格式 - BSQ 格式:每个波段的所有行数据连续存储 - """ - img = spectral.open_image(input_path) - hdr = img.metadata.copy() - n_rows, n_cols, n_bands = img.nrows, img.ncols, img.nbands - new_bands = n_bands // factor # 抽取后的波段数 - - new_hdr = hdr.copy() - new_hdr['samples'] = n_cols - new_hdr['lines'] = n_rows - new_hdr['bands'] = new_bands - new_hdr['data type'] = 12 - new_hdr['interleave'] = 'bsq' - - # 波长抽取(优先使用原始 header 中的波长) - if 'wavelength' in hdr: - waves = np.array(hdr['wavelength']) - if len(waves) >= new_bands * factor: - extracted_waves = waves[offset::factor][:new_bands] - new_hdr['wavelength'] = extracted_waves.tolist() - else: - print("警告: 原始波长数量不足,跳过") - - # FWHM 处理 - if 'fwhm' in hdr: - fwhm = np.array(hdr['fwhm']) - if len(fwhm) >= new_bands * factor: - new_hdr['fwhm'] = fwhm[offset::factor][:new_bands].tolist() - - out_file = output_path + '.bsq' - - # 关键修改:BSQ 格式要求每个波段的所有行连续存储 - # 先处理所有数据到内存缓冲区(按波段组织),再写入文件 - # 对于大文件,我们按波段分批处理 - - print(f"开始处理 {n_rows} 行 x {n_cols} 列 x {new_bands} 波段...") - - # 为每个波段创建一个临时文件(避免内存溢出) - import tempfile - import os - - temp_files = [] - temp_band_data = [] - - try: - # 初始化每个波段的临时文件 - for b in range(new_bands): - fd, temp_path = tempfile.mkstemp(suffix=f'_band_{b}.tmp') - os.close(fd) - temp_files.append(temp_path) - temp_band_data.append(open(temp_path, 'wb')) - - # 第一遍:分块读取,按波段写入临时文件 - for start_row in range(0, n_rows, chunk_lines): - end_row = min(start_row + chunk_lines, n_rows) - print(f"处理行 {start_row}-{end_row-1}...") - - chunk = img.read_subregion((start_row, end_row), (0, n_cols)) - chunk_down = downsample_bands_extract(chunk, factor, offset) - chunk_scaled = chunk_down * scale - chunk_uint16 = np.clip(chunk_scaled, 0, 65535).astype(np.uint16) - - # 将每个波段的数据写入对应的临时文件 - for b in range(new_bands): - band_data = chunk_uint16[:, :, b].tobytes() - temp_band_data[b].write(band_data) - - del chunk, chunk_down, chunk_scaled, chunk_uint16 - - # 关闭所有临时文件 - for f in temp_band_data: - f.close() - - # 第二遍:按 BSQ 格式合并(波段0全部数据 → 波段1全部数据 → ...) - print("合并为 BSQ 格式...") - with open(out_file, 'wb') as fout: - for b in range(new_bands): - with open(temp_files[b], 'rb') as fband: - # 读取该波段的全部行数据并写入最终文件 - while True: - data = fband.read(1024 * 1024) # 1MB 块读取 - if not data: - break - fout.write(data) - print(f" 波段 {b+1}/{new_bands} 写入完成") - - print(f"BSQ 文件写入完成: {out_file}") - - finally: - # 清理临时文件 - for temp_path in temp_files: - try: - os.remove(temp_path) - except: - pass - - # 写入头文件 - envi.write_envi_header(output_path + '.hdr', new_hdr) - print(f"完成!输出: {out_file}") - print(f" 尺寸: {n_rows} 行 x {n_cols} 列 x {new_bands} 波段") - -if __name__ == '__main__': - input_base = r"D:\BaiduNetdiskDownload\yaobao\caijain.hdr" - output_base = r"D:\BaiduNetdiskDownload\yaobao\test" - process_bsq_chunked(input_base, output_base, scale=10000, factor=3, offset=0, chunk_lines=2000)