Add pixi.toml configuration for project dependencies and tasks
- Created a new pixi.toml file to manage project dependencies and tasks. - Defined workspace metadata including authors, channels, name, platforms, and version. - Specified tasks for preprocessing, PSO, DE, and other scripts. - Listed dependencies for Python and various libraries with specific versions. - Configured additional PyPI options for PyTorch installation from a custom index.
This commit is contained in:
parent
264e17b13a
commit
332226f843
19 changed files with 275 additions and 57 deletions
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# SCM syntax highlighting & preventing 3-way merges
|
||||||
|
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -220,3 +220,6 @@ logs/
|
||||||
results/
|
results/
|
||||||
progress.json
|
progress.json
|
||||||
xfr_image_metadata*
|
xfr_image_metadata*
|
||||||
|
# pixi environments
|
||||||
|
.pixi/*
|
||||||
|
!.pixi/config.toml
|
||||||
|
|
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -2,6 +2,5 @@
|
||||||
"python.analysis.extraPaths": [
|
"python.analysis.extraPaths": [
|
||||||
"${workspaceFolder}"
|
"${workspaceFolder}"
|
||||||
],
|
],
|
||||||
"python-envs.defaultEnvManager": "ms-python.python:conda",
|
"python.defaultInterpreterPath": "${workspaceFolder}/.pixi/envs/default/bin/python"
|
||||||
"python-envs.defaultPackageManager": "ms-python.python:conda"
|
|
||||||
}
|
}
|
||||||
80
AGENTS.md
Normal file
80
AGENTS.md
Normal file
|
|
@ -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> -- <args>`(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),輸出 `<vol>/crop/` 與各 level 的 mask,並把仿射鏈寫進 `<vol>/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 <run_id> [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_<script>_<timestamp>.log`;logger 名稱與腳本一致。
|
||||||
|
- **不覆蓋輸出**:存檔用 `utils.helpers.get_unique_filepath` / `save_with_unique_name`(自動 `_1`, `_2` 後綴)。
|
||||||
|
- 檔案命名含螺絲參數時,長度進檔名不帶小數(35.0→35)、直徑保留一位小數(`utils.helpers._fmt_length`)。
|
||||||
|
|
||||||
|
## 多 GPU / 批次
|
||||||
|
|
||||||
|
`run_all_cases.py --gpu N --total_gpus M`:病人依 index % total_gpus 分配;`run_benchmark.py` / `run_de.py` / `run_pso.py` 為 benchmark 腳本,結果 CSV 在 `optimization_benchmark_results_*.csv`。舊腳本的 `base_dir` 指向 `/home/cyrou/...`(可能已遷移),跑前確認路徑。
|
||||||
|
|
||||||
|
## 修改注意事項
|
||||||
|
|
||||||
|
- `references/` 唯讀。
|
||||||
|
- `xfr_image_metadata.json`、`progress.json`、`logs/`、`results/`、`.kilo/` 已 gitignore,勿 commit。
|
||||||
|
- `core/scoring.py`、`imaging/orientation.py` 有 xfr 版本(`cl_score_torch_xfr`、`segment_spinous_process` 等)與原版並存;改評分邏輯時確認呼叫方用哪個版本(objective 目前用 `_xfr`)。
|
||||||
|
- 改 `LABEL_MAP` 或 level 集合時,同步檢查 `xfr_cbt_native.py` 的 label 值定義(L1L=1 … L5R=10)。
|
||||||
|
- GPU 不可用時多數 torch 路徑會 fallback CPU,但速度極慢;跑完整最佳化前確認 CUDA 可用。
|
||||||
72
README.md
Normal file
72
README.md
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# CBT_project(Cortical Bone Trajectory)
|
||||||
|
|
||||||
|
在腰椎 CT(L1–L5)上自動規劃椎弓根螺絲(pedicle screw)軌跡的研究程式:把螺絲建模為圓柱體,用 PSO / DE / Nelder-Mead 最佳化在骨頭分割結果上評分,求得每節脊椎左/右最佳螺絲的位置、角度與尺寸,再映回 native CT 空間輸出可視化結果。
|
||||||
|
|
||||||
|
## 管線
|
||||||
|
|
||||||
|
```
|
||||||
|
CTSpine1K 原始 CT + label
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
xfr_preprocess.py 裁切 / 0.5mm 重取樣 / 方向正規化
|
||||||
|
│ (symmetry plane、upper endplate plane)
|
||||||
|
▼
|
||||||
|
xfr_pso.py / xfr_de.py 每 level 左/右螺絲軌跡最佳化(GPU)
|
||||||
|
│ 圓柱 × 骨頭 mask 評分(皮質骨、VBODY、
|
||||||
|
│ tip 處罰、入口端 0.5mm 豁免)
|
||||||
|
▼
|
||||||
|
xfr_check_spinous.py 棘突檢查 / 驗證
|
||||||
|
xfr_cbt_native.py 映回 native CT → cbt.nii.gz + X 光投影片
|
||||||
|
```
|
||||||
|
|
||||||
|
- 最佳化參數:`pos = [z, y, x, azimuth°, altitude°, 直徑 mm, 長度 mm]`,直徑/長度吸附到臨床允許值。
|
||||||
|
- 評分函數:`core/objective.py` 的 `OptimizationContext` + `make_objective_function[_xfr]`。
|
||||||
|
- 映回 native 以各 volume 的 `transform.json` 仿射鏈為單一事實來源(`imaging/transforms.py`)。
|
||||||
|
|
||||||
|
## 環境
|
||||||
|
|
||||||
|
- Python 環境管理:**pixi**(`pixi.toml`;conda env `cbt` 已棄用)
|
||||||
|
- 首次建置 / 更新:`pixi install`
|
||||||
|
- 主要依賴:`torch`(CUDA 12.6 wheel)、`SimpleITK`、`scipy`、`scikit-learn`、`numpy`、`nibabel`、`tinydb`、`pyswarm`、`matplotlib`
|
||||||
|
- 多 GPU 批次:`run_all_cases.py --gpu N --total_gpus M`
|
||||||
|
|
||||||
|
## 執行
|
||||||
|
|
||||||
|
入口腳本皆有 pixi task(`pixi run <task> -- <args>`,見 `pixi.toml` 的 `[tasks]`);或 `pixi shell` 後直接執行(腳本 shebang 為 `#!/usr/bin/env python`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 預處理(全部 volume;或 --max-images 取前 N 支)
|
||||||
|
pixi run preprocess -- [--max-images N] [--output-dir DIR]
|
||||||
|
|
||||||
|
# 最佳化(缺省跑標準化目錄內所有 volume 的 L1–L5)
|
||||||
|
pixi run pso -- [volume_id [level]] [--screw-shape cone|cylinder]
|
||||||
|
pixi run de -- [volume_id [level]]
|
||||||
|
|
||||||
|
# 棘突檢查
|
||||||
|
pixi run check-spinous
|
||||||
|
|
||||||
|
# 映回 native CT(run_id 為最佳化輸出的日期時間目錄)
|
||||||
|
pixi run cbt-native -- <run_id> [volumes...] [--date D]
|
||||||
|
```
|
||||||
|
|
||||||
|
資料路徑(CTSpine1K 原始檔、標準化輸出、最終輸出)hardcode 在各 `xfr_*.py` 頂部,跨機器/世代執行時先確認這些常數;元數據存於 `xfr_image_metadata.json`(TinyDB),日誌在 `logs/`,結果 CSV 在 `results/`。
|
||||||
|
|
||||||
|
## 目錄結構
|
||||||
|
|
||||||
|
| 路徑 | 說明 |
|
||||||
|
|---|---|
|
||||||
|
| `core/` | 圓柱生成(torch)、中心線相交、評分(`scoring.py`)、objective、optimizer(PSO/DE/NM) |
|
||||||
|
| `imaging/` | NIfTI I/O、預處理、重取樣、仿射、方向正規化、分割、transform 鏈 |
|
||||||
|
| `visualization/` | 四視角螺絲圖(`res_bone_figure`)、3D 繪圖、圓柱存 NIfTI / overlay 到 CT |
|
||||||
|
| `config/` | `constant.py`(LABEL_MAP、螺絲尺寸、OVERLAP_THRESH、spacing)、`device.py` |
|
||||||
|
| `utils/` | NFS 重試、唯一檔名、輔助函數 |
|
||||||
|
| `xfr_*.py` | 現世代管線入口腳本 |
|
||||||
|
| `run_*.py` | 舊世代多 GPU 批次 / benchmark 腳本 |
|
||||||
|
| `references/` | 參考文獻 PDF |
|
||||||
|
| `experiment.ipynb` | 實驗 notebook |
|
||||||
|
|
||||||
|
## 輸出
|
||||||
|
|
||||||
|
- 每 volume 的螺絲:標準化目錄下 `Output/<run_id>/<vol>/` 的四視角 PNG 與 CSV。
|
||||||
|
- Native 空間:`Output_dir/<run_date>/<vol>/cbt.nii.gz`(label 1–10 = L1L…L5R)與 x-ap / x-lat / z-ap / z-lat X 光投影片(含螺絲標註版)。
|
||||||
|
- 驗收標準見 `xfr_cbt_native.py` docstring(螺絲柱 in-label ≥ 92%、2-voxel 膨脹 ≥ 98%)。
|
||||||
BIN
pixi.lock
generated
Normal file
BIN
pixi.lock
generated
Normal file
Binary file not shown.
39
pixi.toml
Normal file
39
pixi.toml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
[workspace]
|
||||||
|
authors = ["Xiao Furen <xfr@dr.com>"]
|
||||||
|
channels = ["conda-forge"]
|
||||||
|
name = "CBT_project"
|
||||||
|
platforms = ["linux-64"]
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
# 管線入口腳本(從 repo 根目錄執行;追加參數用 `pixi run <task> -- <args>`)
|
||||||
|
[tasks]
|
||||||
|
preprocess = "python xfr_preprocess.py"
|
||||||
|
pso = "python xfr_pso.py"
|
||||||
|
de = "python xfr_de.py"
|
||||||
|
check-spinous = "python xfr_check_spinous.py"
|
||||||
|
rerender-spinous = "python xfr_rerender_spinous.py"
|
||||||
|
cbt-native = "python xfr_cbt_native.py"
|
||||||
|
record-planes = "python xfr_record_planes.py"
|
||||||
|
plot-level = "python xfr_plot_level.py"
|
||||||
|
|
||||||
|
# conda 端只裝 python(與原 conda env cbt 的 3.14.3 同代),
|
||||||
|
# 其餘全部走 pypi,版本對齊原 env。
|
||||||
|
[dependencies]
|
||||||
|
python = "3.14.*"
|
||||||
|
|
||||||
|
# torch 從 pytorch cu126 index 取(2.11.0+cu126);其餘走 PyPI。
|
||||||
|
# 專案未使用 torchvision / torchaudio,原 env 有裝但管線未 import;
|
||||||
|
# 需要時在此加回並沿用同一 index 即可。
|
||||||
|
[pypi-dependencies]
|
||||||
|
numpy = "==2.4.4"
|
||||||
|
scipy = "==1.17.1"
|
||||||
|
scikit-learn = "==1.8.0"
|
||||||
|
matplotlib = "==3.10.8"
|
||||||
|
nibabel = "==5.4.2"
|
||||||
|
simpleitk = "==2.5.3"
|
||||||
|
tinydb = "==4.9.0"
|
||||||
|
pyswarm = "==0.6"
|
||||||
|
torch = "==2.11.0"
|
||||||
|
|
||||||
|
[pypi-options]
|
||||||
|
extra-index-urls = ["https://download.pytorch.org/whl/cu126"]
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""把 PSO 找到的 CBT 螺絲位置(旋轉 0.5mm 標準系)映回原始
|
"""把 PSO 找到的 CBT 螺絲位置(旋轉 0.5mm 標準系)映回原始
|
||||||
(未旋轉、未重取樣)native CT 空間,整支 volume 各 level 的螺絲
|
(未旋轉、未重取樣)native CT 空間,整支 volume 各 level 的螺絲
|
||||||
(L1-L5 x L/R,最多 10 支)存成單一 label 體積:
|
(L1-L5 x L/R,最多 10 支)存成單一 label 體積:
|
||||||
|
|
||||||
Output_dir/<run_date>/<volume_id>/cbt.nii.gz
|
Output_dir/<run_date>/<volume_id>/cbt.nii.gz
|
||||||
Output_dir/<run_date>/<volume_id>/x-ap.jpg 合成前後(AP) X 光投影视圖
|
Output_dir/<run_date>/<volume_id>/x-ap.jpg 合成前後(AP) X 光投影视圖(帶 R/L 方向標記、無螺絲標註)
|
||||||
Output_dir/<run_date>/<volume_id>/x-lat.jpg 合成側位 X 光投影视圖
|
Output_dir/<run_date>/<volume_id>/x-lat.jpg 合成側位 X 光投影视圖(帶 A/P 方向標記、無螺絲標註)
|
||||||
|
Output_dir/<run_date>/<volume_id>/z-ap.jpg 同 x-ap,另加螺絲標註(AP bounding box)
|
||||||
|
Output_dir/<run_date>/<volume_id>/z-lat.jpg 同 x-lat,另加螺絲標註(側位 margin 環)
|
||||||
(只投影脊椎骨 + 螺絲、不含軟組織;
|
(只投影脊椎骨 + 螺絲、不含軟組織;
|
||||||
見 render_xray_projections)
|
見 render_xray_projections)
|
||||||
|
|
||||||
|
|
@ -79,6 +81,7 @@ META_DB = os.path.join(_PROJ_DIR, 'xfr_image_metadata.json')
|
||||||
LEVELS = ('L1', 'L2', 'L3', 'L4', 'L5')
|
LEVELS = ('L1', 'L2', 'L3', 'L4', 'L5')
|
||||||
LEVEL_LABEL_VAL = {v: int(k) for k, v in LABEL_MAP.items() if v in LEVELS} # {'L1': 20, ...}
|
LEVEL_LABEL_VAL = {v: int(k) for k, v in LABEL_MAP.items() if v in LEVELS} # {'L1': 20, ...}
|
||||||
PROJ_FILENAME = {'ap': 'x-ap.jpg', 'lateral': 'x-lat.jpg'}
|
PROJ_FILENAME = {'ap': 'x-ap.jpg', 'lateral': 'x-lat.jpg'}
|
||||||
|
PROJ_FILENAME_MARKED = {'ap': 'z-ap.jpg', 'lateral': 'z-lat.jpg'}
|
||||||
|
|
||||||
logger = logging.getLogger('xfr_cbt_native')
|
logger = logging.getLogger('xfr_cbt_native')
|
||||||
|
|
||||||
|
|
@ -291,7 +294,8 @@ def render_xray_projections(cbt_path, ct_path, out_dir,
|
||||||
x-lat = 右下圖(畫面右=前位、A 標記在右)、頂恆=頭端;
|
x-lat = 右下圖(畫面右=前位、A 標記在右)、頂恆=頭端;
|
||||||
非軸對齊 direction(離對角 ±1 的 off-diagonal)會 warning(投影軸錯位)。
|
非軸對齊 direction(離對角 ±1 的 off-diagonal)會 warning(投影軸錯位)。
|
||||||
spacing 各軸不等時先重取樣到 min(spacing) 各向同性格,維持投影 aspect ratio。
|
spacing 各軸不等時先重取樣到 min(spacing) 各向同性格,維持投影 aspect ratio。
|
||||||
輸出 x-ap.jpg / x-lat.jpg,回傳 {view: jpg_path}。"""
|
輸出 x-ap.jpg / x-lat.jpg(帶方向標記、無螺絲標註)與
|
||||||
|
z-ap.jpg / z-lat.jpg(另有螺絲標註),回傳 {view: x-path, view+'_marked': z-path}。"""
|
||||||
import matplotlib
|
import matplotlib
|
||||||
matplotlib.use('Agg')
|
matplotlib.use('Agg')
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
@ -391,15 +395,14 @@ def render_xray_projections(cbt_path, ct_path, out_dir,
|
||||||
img = np.clip(bone + metal_img, 0, 1)
|
img = np.clip(bone + metal_img, 0, 1)
|
||||||
ll, rl = markers[view]
|
ll, rl = markers[view]
|
||||||
h, w = img.shape
|
h, w = img.shape
|
||||||
fig, ax = plt.subplots(figsize=(8.0 * w / h, 8.0), dpi=110)
|
|
||||||
# 螺絲(label 1-10 = L1L L1R … L5L L5R,奇=L 偶=R)本身維持白色;
|
# 螺絲(label 1-10 = L1L L1R … L5L L5R,奇=L 偶=R)本身維持白色;
|
||||||
# 標註座標與投影视圖同系,flip 依 CT direction(上方 flip_rows/flip_cols)
|
# 標註座標與投影视圖同系,flip 依 CT direction(上方 flip_rows/flip_cols)
|
||||||
n_z = zs.stop - zs.start
|
n_z = zs.stop - zs.start
|
||||||
n_x = xs.stop - xs.start
|
n_x = xs.stop - xs.start
|
||||||
n_y = ys.stop - ys.start
|
n_y = ys.stop - ys.start
|
||||||
if view == 'ap':
|
if view == 'ap':
|
||||||
# AP:每支螺絲一個 bounding box
|
# AP:z-* 才畫每支螺絲一個 bounding box
|
||||||
ax.imshow(img, cmap='gray', interpolation='nearest')
|
def _annotate(ax):
|
||||||
for val in range(1, 11):
|
for val in range(1, 11):
|
||||||
ms = cbt_arr == val
|
ms = cbt_arr == val
|
||||||
if not ms.any():
|
if not ms.any():
|
||||||
|
|
@ -422,8 +425,9 @@ def render_xray_projections(cbt_path, ct_path, out_dir,
|
||||||
fill=False,
|
fill=False,
|
||||||
edgecolor=_SCREW_ANNOT_RGB['L' if val % 2 == 1 else 'R'],
|
edgecolor=_SCREW_ANNOT_RGB['L' if val % 2 == 1 else 'R'],
|
||||||
linewidth=1.5, zorder=5))
|
linewidth=1.5, zorder=5))
|
||||||
|
annot_img = None
|
||||||
else:
|
else:
|
||||||
# Lateral:每支螺絲投影的外緣 2 px 環(margin)著色
|
# Lateral:每支螺絲投影的外緣 2 px 環(margin)著色(z-* 用)
|
||||||
rgb = np.stack([img] * 3, axis=-1)
|
rgb = np.stack([img] * 3, axis=-1)
|
||||||
for val in range(1, 11):
|
for val in range(1, 11):
|
||||||
ms = cbt_arr == val
|
ms = cbt_arr == val
|
||||||
|
|
@ -442,7 +446,21 @@ def render_xray_projections(cbt_path, ct_path, out_dir,
|
||||||
mask[r.astype(int), c.astype(int)] = True
|
mask[r.astype(int), c.astype(int)] = True
|
||||||
outline = mask & ~binary_erosion(mask, iterations=2)
|
outline = mask & ~binary_erosion(mask, iterations=2)
|
||||||
rgb[outline] = _SCREW_ANNOT_RGB['L' if val % 2 == 1 else 'R']
|
rgb[outline] = _SCREW_ANNOT_RGB['L' if val % 2 == 1 else 'R']
|
||||||
ax.imshow(rgb, interpolation='nearest')
|
|
||||||
|
def _annotate(ax):
|
||||||
|
pass
|
||||||
|
annot_img = rgb
|
||||||
|
|
||||||
|
# 兩個版本(皆帶 R/L 或 A/P 方向標記):
|
||||||
|
# x-* 只有白色螺絲、無螺絲標註;z-* 另有螺絲標註(AP box / Lateral margin)
|
||||||
|
def _save(fname, with_annot):
|
||||||
|
fig, ax = plt.subplots(figsize=(8.0 * w / h, 8.0), dpi=110)
|
||||||
|
img_show = annot_img if (with_annot and annot_img is not None) else img
|
||||||
|
ax.imshow(img_show,
|
||||||
|
cmap=None if img_show is annot_img else 'gray',
|
||||||
|
interpolation='nearest')
|
||||||
|
if with_annot:
|
||||||
|
_annotate(ax)
|
||||||
ax.set_title(f'{view.upper()} projection (spine + screws) - {vid}',
|
ax.set_title(f'{view.upper()} projection (spine + screws) - {vid}',
|
||||||
color='white', fontsize=13)
|
color='white', fontsize=13)
|
||||||
ax.text(0.01, 0.98, ll, transform=ax.transAxes, color='cyan', fontsize=13,
|
ax.text(0.01, 0.98, ll, transform=ax.transAxes, color='cyan', fontsize=13,
|
||||||
|
|
@ -451,11 +469,16 @@ def render_xray_projections(cbt_path, ct_path, out_dir,
|
||||||
va='top', ha='right', fontweight='bold')
|
va='top', ha='right', fontweight='bold')
|
||||||
ax.axis('off')
|
ax.axis('off')
|
||||||
fig.tight_layout(pad=0.5)
|
fig.tight_layout(pad=0.5)
|
||||||
p = os.path.join(out_dir, PROJ_FILENAME[view])
|
p = os.path.join(out_dir, fname)
|
||||||
fig.savefig(p, format='jpg', facecolor='black')
|
fig.savefig(p, format='jpg', facecolor='black')
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
return p
|
||||||
|
p = _save(PROJ_FILENAME[view], False)
|
||||||
paths[view] = p
|
paths[view] = p
|
||||||
logger.info(f'{vid}: {view} projection -> {p}')
|
logger.info(f'{vid}: {view} projection -> {p}')
|
||||||
|
p = _save(PROJ_FILENAME_MARKED[view], True)
|
||||||
|
paths[view + '_marked'] = p
|
||||||
|
logger.info(f'{vid}: {view} projection (marked) -> {p}')
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""
|
"""
|
||||||
檢查各 (volume, level) 的棘突(midline 後側構造)是否缺如
|
檢查各 (volume, level) 的棘突(midline 後側構造)是否缺如
|
||||||
(先前手術如 laminectomy / 棘突切除,例:0005 L5、0770 L5)。
|
(先前手術如 laminectomy / 棘突切除,例:0005 L5、0770 L5)。
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""把標準化 grid 上的 segmentation mask 反向映射回 data_root 原始 CT grid。
|
"""把標準化 grid 上的 segmentation mask 反向映射回 data_root 原始 CT grid。
|
||||||
|
|
||||||
座標參數來自 xfr_preprocess 記錄的 <vol_dir>/transform.json(每 level:
|
座標參數來自 xfr_preprocess 記錄的 <vol_dir>/transform.json(每 level:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""一次性的 layout 遷移:把已產出 volume 的「未旋轉 <level> 檔」移入 crop/ 子資料夾。
|
"""一次性的 layout 遷移:把已產出 volume 的「未旋轉 <level> 檔」移入 crop/ 子資料夾。
|
||||||
|
|
||||||
新 layout(2026-09-08 起 xfr_preprocess 直接產出,無須再跑本腳本):
|
新 layout(2026-09-08 起 xfr_preprocess 直接產出,無須再跑本腳本):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
"""把每個 lumbar level 的 rotated/{L}_label.nii.gz(旋轉座標,
|
"""把每個 lumbar level 的 rotated/{L}_label.nii.gz(旋轉座標,
|
||||||
1=VBODY 椎體、2=SP 棘突、3=other bone)反旋轉回原始座標,
|
1=VBODY 椎體、2=SP 棘突、3=other bone)反旋轉回原始座標,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""
|
"""
|
||||||
為每個 (volume, level) 的骨頭遮罩({level}_binary_sdf.nii.gz,缺則 _binary)繪製
|
為每個 (volume, level) 的骨頭遮罩({level}_binary_sdf.nii.gz,缺則 _binary)繪製
|
||||||
X-ray 四視角圖(不畫螺絲),存到 Output/{date}/{volume}/。
|
X-ray 四視角圖(不畫螺絲),存到 Output/{date}/{volume}/。
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""補記既有 volume 的 transform.json rotated 段平面(sym / endplate)。
|
"""補記既有 volume 的 transform.json rotated 段平面(sym / endplate)。
|
||||||
|
|
||||||
core/optimizer 的 xfr 管線(run_pso_torch_xfr / run_de_torch_xfr)不再於影像
|
core/optimizer 的 xfr 管線(run_pso_torch_xfr / run_de_torch_xfr)不再於影像
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""掃 standardize 輸出目錄中「前後(AP)方向翻轉」的個案(prone 伏位掃描,
|
"""掃 standardize 輸出目錄中「前後(AP)方向翻轉」的個案(prone 伏位掃描,
|
||||||
前側落在 y 小側,例:colon 0003),供重跑修正用。
|
前側落在 y 小側,例:colon 0003),供重跑修正用。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/home/xfr/.conda/envs/cbt/bin/python
|
#!/usr/bin/env python
|
||||||
"""
|
"""
|
||||||
從 optimizer 存的 output.csv 重新渲染既有螺絲模式四視角圖(X-ray 圖)。
|
從 optimizer 存的 output.csv 重新渲染既有螺絲模式四視角圖(X-ray 圖)。
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue