diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..997504b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# SCM syntax highlighting & preventing 3-way merges +pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff diff --git a/.gitignore b/.gitignore index c0a009b..8b8048e 100644 --- a/.gitignore +++ b/.gitignore @@ -220,3 +220,6 @@ logs/ results/ progress.json xfr_image_metadata* +# pixi environments +.pixi/* +!.pixi/config.toml diff --git a/.vscode/settings.json b/.vscode/settings.json index f9c24fe..b9a9329 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,5 @@ "python.analysis.extraPaths": [ "${workspaceFolder}" ], - "python-envs.defaultEnvManager": "ms-python.python:conda", - "python-envs.defaultPackageManager": "ms-python.python:conda" + "python.defaultInterpreterPath": "${workspaceFolder}/.pixi/envs/default/bin/python" } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..cbabb39 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,80 @@ +# AGENTS.md + +本文件提供 AI 編碼代理(Kilo / Claude 等)在 `CBT_project` 內工作時的指引。 + +## 專案概述 + +CBT(Cortical Bone Trajectory,皮質骨軌跡):在腰椎 CT(L1–L5)上自動規劃椎弓根螺絲(pedicle screw)軌跡。將螺絲建模為圓柱體,以 PSO / DE / Nelder-Mead 最佳化在骨頭 mask 上評分(皮質骨接觸、椎體 VBODY 獎勵、tip 處罰、入口端 0.5mm 豁免),取得最佳位置/角度/尺寸後再映回 native CT 空間。 + +## 執行環境 + +- Python 環境:**pixi**(conda env `cbt` 已棄用)。環境在 `.pixi/envs/default/`,python 3.14;`pixi.toml` 定義全部依賴與 tasks。VS Code 已設定 `python.defaultInterpreterPath` 指向 pixi env。 +- `xfr_*.py` 的 shebang 為 `#!/usr/bin/env python`,需先 `pixi shell` / `pixi run`(或 VS Code 選到 pixi interpreter)才有對的 python。 +- 重建環境:`pixi install`(torch 走 `extra-index-urls` 的 pytorch cu126 index,版本 pin 在 `pixi.toml`)。 +- 主要依賴(皆 pypi):`torch`(CUDA,GPU 最佳化)、`SimpleITK`、`scipy`、`scikit-learn`、`numpy`、`nibabel`、`tinydb`、`pyswarm`、`matplotlib`。conda 端只有 python。 +- **無測試、無 lint 設定**。驗證方式:跑小批量 volume、檢查 logs/ 與結果 CSV、以及螺絲柱 in-label 比例(xfr_cbt_native 的 docstring 有驗收標準)。 + +## 資料位置(皆在 repo 之外,勿把大檔 commit 進 repo) + +| 用途 | 路徑 | +|---|---| +| 原始 CT / label(CTSpine1K) | `/mnt/1220/Public/dataset/Spine/CTSpine1K/{data,label}/`(子資料夾:`colon`(=conlon)、`COVID-19`、`HNSCC-3DCT-RT_neck`、`liver`(=Liver)) | +| 標準化輸出(current) | `/mnt/1248/open2/cyrou/CBT/Seg/Resample/standardized-xfr-3/` | +| 最終輸出(cbt.nii.gz、X 光投影) | `/mnt/1248/open/cyrou/Output/` | +| 元資料庫(TinyDB) | repo 根目錄 `xfr_image_metadata.json`(table `images`,key = volume `name`) | +| 進度 / 日誌 / 結果 | `progress.json`、`logs/`、`results/`(皆已被 gitignore) | + +`xfr_*.py` 頂部有 hardcode 的資料路徑(`standardized_dir`、`data_root`、`label_root`、`Output_dir`);跑別世代資料時改這些常數,不要假設 CLI 可覆蓋。 + +## 目錄結構 + +``` +core/ 圓柱生成(torch)、中心線相交、評分、objective、optimizer(PSO/DE/NM) +imaging/ nifti_io、preprocessing、resample、affine、orientation、segmentation、transforms +visualization/ res_bone_figure(四視角螺絲圖)、res_plot_3d、res_cyl_nifti(存 NIfTI)、res_cyl_to_CT(overlay) +config/ constant.py(LABEL_MAP、螺絲尺寸、OVERLAP_THRESH、DEFAULT_SPACING)、device.py +utils/ helpers.py(retry_robust、unique filepath、save_with_unique_name) +xfr_*.py 現世代管線入口腳本(見下) +run_*.py 舊世代多 GPU 批次 / benchmark 腳本(base_dir 不同,較少使用) +references/ 文獻 PDF(唯讀參考,勿改) +experiment.ipynb 實驗 notebook +``` + +## 管線流程與入口腳本 + +入口腳本都有對應 pixi task:`pixi run -- `(task 清單在 `pixi.toml [tasks]`:`preprocess`、`pso`、`de`、`check-spinous`、`rerender-spinous`、`cbt-native`、`record-planes`、`plot-level`)。以下直接列腳本名,加 `./` 前缀在 pixi 環境內亦可執行。 + +1. **預處理**:`xfr_preprocess.py [--max-images N] [--output-dir DIR]` + 裁切各 level、重取樣到 0.5mm、方向正規化(best_symmetry_plane / best_upper_endplate_plane),輸出 `/crop/` 與各 level 的 mask,並把仿射鏈寫進 `/transform.json`。 + 跳過條件:z spacing > 4mm、lumbar label < 2、椎弓根寬度 > 18mm(clip 並標 `_clipped`)。 +2. **最佳化**:`xfr_pso.py [volume_id [level]] [--screw-shape cone|cylinder]`(DE 版:`xfr_de.py`) + 每 level 左/右各跑一支 PSO,輸出螺絲參數與 `render_bone_figure` 四視角圖、CSV。 +3. **驗證**:`xfr_check_spinous.py`、`xfr_rerender_spinous.py`(棘突檢查/重繪)。 +4. **映回 native**:`xfr_cbt_native.py [volumes...] [--date D]` + 用 `transform.json` 正向鏈把螺絲映回 native CT,輸出 `cbt.nii.gz`(label 1–10 = L1L…L5R)與 x-ap/x-lat/z-ap/z-lat 投影圖。 +5. **輔助**:`xfr_record_planes.py`(記錄平面入 transform.json)、`xfr_inverse_transform.py`(單一 mask 逆向映射)、`xfr_orig_labels.py`(原始 label 對齊)、`xfr_reprocess_ap.py`(AP 方向修正)、`xfr_plot_level.py`(單 level 圖)、`xfr_migrate_crop.py`(舊佈局遷移到 crop/)。 + +## 程式慣例(重要) + +- **註解 / docstring 用繁體中文**,識別符號用英文。新增碼保持同風格。 +- **座標系**:numpy 體積為 `(Z, Y, X)`;螺絲參數 `pos = [z, y, x, az°, alt°, d_mm, L_mm]`(xfr 變體為 `[z, x, az°, alt°, d, L]`,y 由 `y_indices` surface 推導,見 `core/optimizer.py::get_first_nonzero_y`)。spacing 為 `[sx, sy, sz]`,預設 0.5 isotropic。 +- **Objective API**:新碼一律用 `core/objective.py` 的 `OptimizationContext` + `make_objective_function[_xfr]`。**禁止依賴 module globals**(`set_global_context` / `objective_function` 是 legacy 兼容路徑,保留勿擴充)。 +- **螺絲尺寸離散化**:`snap_to_discrete_values`(`config.constant.ALLOWED_DIAMETERS/ALLOWED_LENGTHS`);xfr 管線用 `snap_to_discrete_values_xfr`(`core/cylinder.py` 的 `points` 允許 (d, L) 組合表)。xfr 還做 0.9/0.1 混合(`diameter_loss = .9*discrete + .1*raw`)。 +- **transform.json 是 native 映射的單一事實來源**(`imaging.transforms.original_to_source`)。fallback 重算路徑僅供舊世代 volume;改映射邏輯時先讀 `xfr_cbt_native.py` docstring 的驗證紀錄。 +- **元資料庫是 TinyDB**:用 `xfr_preprocess.py::_upsert_by_name` 一類的 upsert 寫入;勿手改 JSON 結構(legacy 遷移邏輯對結構敏感,見 `_migrate_legacy_metadata`)。 +- **NFS 瞬時錯誤**:輸出樹在 NFS 上,`os.makedirs`/寫檔可能短暫 ENOENT/EEXIST。沿用 `utils.helpers.retry_robust` 或 `core/optimizer.py::_makedirs_retry` 的重試模式;勿裸 `os.makedirs` 建輸出目錄。 +- **日誌**:腳本寫 `logs/xfr_