Metadata-Version: 2.4
Name: ponychart-classifier
Version: 0.6.1
Summary: Multi-label image classifier for PonyChart character identification.
Project-URL: Homepage, https://github.com/Kuan-Lun/ponychart-classifier
Project-URL: Tracker, https://github.com/Kuan-Lun/ponychart-classifier/issues
Author: Kuan-Lun Wang
License: GNU Affero General Public License v3
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: <3.14,>=3.11
Requires-Dist: certifi>=2026.2.25; sys_platform == 'darwin'
Requires-Dist: numpy>=2.4.4
Requires-Dist: onnxruntime>=1.24.4
Requires-Dist: opencv-python>=4.13.0.92
Provides-Extra: dev
Requires-Dist: black>=26.3.1; extra == 'dev'
Requires-Dist: mypy>=1.20.0; extra == 'dev'
Requires-Dist: pymarkdownlnt>=0.9.36; extra == 'dev'
Requires-Dist: pytest>=9.0.3; extra == 'dev'
Requires-Dist: ruff>=0.15.10; extra == 'dev'
Provides-Extra: train
Requires-Dist: flask>=3.1.3; extra == 'train'
Requires-Dist: onnx>=1.21.0; extra == 'train'
Requires-Dist: onnxscript>=0.6.2; extra == 'train'
Requires-Dist: pillow>=12.2.0; extra == 'train'
Requires-Dist: psutil>=7.2.2; extra == 'train'
Requires-Dist: scikit-learn>=1.8.0; extra == 'train'
Requires-Dist: torch>=2.11.0; extra == 'train'
Requires-Dist: torchvision>=0.26.0; extra == 'train'
Description-Content-Type: text/markdown

# PonyChart Classifier

PonyChart 角色辨識模型，用於自動辨識 HentaiVerse 戰鬥中出現的 PonyChart 圖片中的角色。

## 目錄結構

```text
ponychart-classifier/
├── app/
│   └── label_images/                  # 圖片標註工具 (Tkinter GUI)
│       ├── __main__.py                # 進入點
│       ├── app.py                     # LabelApp 主應用
│       ├── analysis.py                # 模型分析 (背景推論)
│       ├── constants.py               # GUI 常數
│       ├── crop_handler.py            # 裁切處理
│       ├── data_viewer/               # 資料概況 / 模型資訊 / 分析結果視窗
│       │   ├── __init__.py            # public viewers re-export
│       │   ├── viewers.py             # _BaseViewer 及三個 public viewer
│       │   ├── extractors.py          # checkpoint 載入與資料萃取
│       │   ├── stats.py               # 原圖樣本聚合統計
│       │   ├── widgets.py             # Tk 排版 helper
│       │   └── sections/              # 可組合的 Section（Protocol + 實作）
│       │       ├── __init__.py
│       │       ├── changes.py         # 資料變更摘要
│       │       ├── distribution_test.py # 分布檢定
│       │       ├── hyperparams.py     # 超參數顯示
│       │       ├── image_counts.py    # 圖片數量統計
│       │       ├── model_arch.py      # 模型架構資訊
│       │       ├── notice.py          # 通知訊息
│       │       ├── split_counts.py    # 資料分割統計
│       │       └── val_f1.py          # 驗證 F1 分數
│       ├── file_actions.py            # 批次檔案操作
│       ├── file_ops.py                # 檔案操作
│       ├── filter_builder.py          # 篩選條件建構
│       ├── filter_panel.py            # 篩選面板 UI
│       ├── image_viewer.py            # 圖片顯示元件
│       ├── label_store.py             # 標籤儲存
│       └── navigator.py              # 圖片導覽
├── src/
│   └── ponychart_classifier/          # PyPI 套件
│       ├── __init__.py                # 公開 API (predict, update, preload, get_thresholds)
│       ├── model_spec.py              # 訓練與推論共用模型規格
│       ├── inference/                 # 推論 package
│       │   ├── __init__.py            # inference API re-export
│       │   ├── classifier.py          # PonyChartClassifier
│       │   ├── artifacts.py           # runtime cache 路徑、下載、ETag
│       │   ├── preprocessing.py       # 影像前處理
│       │   ├── label_selection.py     # 閾值篩選與 top-k 規則
│       │   └── results.py             # PredictionResult / ClassThresholds
│       ├── py.typed                   # PEP 561 type marker
│       ├── stats/                     # 多項分布適合度檢定
│       │   ├── __init__.py            # 公開 API re-export
│       │   ├── asymptotic.py          # 漸近檢定 (chi-square / G-test)
│       │   ├── exact.py               # 精確檢定 (全排列枚舉)
│       │   ├── gof.py                 # 統一入口 goodness_of_fit_test()
│       │   ├── result.py              # GoFTestResult 資料類別
│       │   └── statistics.py          # 檢定統計量 (Pearson / G / logpmf)
│       └── training/                  # 訓練函式庫
│           ├── __init__.py            # Re-export 所有 symbol
│           ├── constants.py           # 常數與訓練超參數 (single source of truth)
│           ├── device.py              # 裝置偵測
│           ├── dataset.py             # 資料載入、Dataset、transforms
│           ├── model.py               # Backbone registry + build_model()
│           ├── training.py            # 訓練迴圈、evaluate、threshold 優化
│           ├── checkpoint.py          # Checkpoint val_f1 重新計算與更新
│           ├── sampling.py            # 樣本載入與平衡
│           ├── splitting.py           # Hash-based group splitting
│           ├── log_helpers.py         # 日誌輔助
│           ├── script_utils.py        # 腳本共用工具
│           ├── experiment_io.py       # 跨機器實驗結果 I/O
│           └── export.py              # ONNX 匯出
├── cli/                               # CLI 工具 (python -m cli.<name>)
│   ├── experiment.py                  # ExperimentCLI 抽象基底 (Template Method)
│   ├── training_runner.py             # 可組合的訓練管線
│   ├── compare_resolution/            # 輸入解析度比較
│   ├── compare_backbones/             # Backbone 架構比較
│   ├── compare_aspect_ratio/          # 長寬比 (正方形 vs 長方形) 比較
│   ├── search_batch_lr/               # Batch size / LR 超參數搜尋
│   ├── benchmark_cpu_inference/       # CPU 推論延遲 benchmark
│   └── analyze_augmentations/         # 資料增強 ablation study
├── tests/                             # 測試套件 (pytest)
│   ├── test_stats.py                  # stats 模組整合測試
│   └── stats/                         # stats 模組單元測試
│       ├── test_compositions.py
│       ├── test_convergence.py
│       ├── test_gof_advanced.py
│       ├── test_gof_basic.py
│       └── test_helpers.py
├── scripts/                           # 開發用腳本 (不隨套件發佈)
│   ├── train.py                       # 模型訓練腳本
│   ├── rebuild-env.sh                 # 重建 .venv 與快取
│   ├── compare_crops.py               # 裁切圖片效果分析
│   ├── compare_pos_weight.py          # pos_weight 效果比較
│   ├── compare_resume_scratch.py      # Resume vs from-scratch 分析
│   ├── evaluate_holdout.py            # Holdout 評估
│   ├── fit_cards.py                   # PonyChart 角色分布模型擬合
│   ├── learning_curve.py              # Learning curve 分析 + power-law 外推
│   └── profile_dataloader.py          # DataLoader 效能分析
├── artifacts/                         # 訓練輸出 model.onnx / thresholds.json (gitignored)
├── rawimage/                          # 訓練用原始圖片 (PNG)
│   ├── labels.json                    # 標註資料 {"1/twilight/filename.png": [1,3]}
│   └── checkpoint.pt                  # PyTorch checkpoint (resume 訓練用)
├── results/                           # CLI 實驗結果 JSON 輸出目錄
├── mypy.ini                           # MyPy strict 設定
├── pyproject.toml
├── uv.lock
└── README.md
```

## 標籤對照

| 編號 | 角色 |
| ---- | ---- |
| 1 | Twilight Sparkle |
| 2 | Rarity |
| 3 | Fluttershy |
| 4 | Rainbow Dash |
| 5 | Pinkie Pie |
| 6 | Applejack |

## 安裝

```bash
# 推論用 (hbrowser 會自動安裝)
uv pip install ponychart-classifier

# 開發用 (包含訓練依賴)
uv pip install -e ".[train]"
```

### 環境損毀時重建

Python 版本升級後若 `uv run black` / `uv run mypy` 等工具出現
`ModuleNotFoundError: No module named '..._mypyc'` 這類錯誤，代表
mypyc 編譯的 extension 找不到內部模組，執行下列指令把 `.venv`
與所有快取重建：

```bash
./scripts/rebuild-env.sh
```

## 使用方式

```python
from ponychart_classifier import (
    clear_artifacts,
    predict,
    preload,
    update,
    get_thresholds,
)
from ponychart_classifier import PonyChartClassifier, PredictionResult, ClassThresholds

# 預先載入模型
preload()

# 檢查並更新模型至最新版本（比對 ETag，有新版才下載）
updated: bool = update()

# 預測圖片中的角色
result: PredictionResult = predict("path/to/image.png")
print(result.labels)  # frozenset({'Rarity', 'Fluttershy'})
print(result.rarity)  # 0.95
print(result.twilight_sparkle)  # 0.02

# 取得各角色的分類閾值
thresholds: ClassThresholds = get_thresholds()

# 清除 runtime cache，下一次載入時會重新下載
clear_artifacts()
```

也可以直接使用 `PonyChartClassifier` 類別：

```python
from ponychart_classifier import PonyChartClassifier

classifier = PonyChartClassifier(
    model_path="artifacts/model.onnx",
    thresholds_path="artifacts/thresholds.json",
)
result = classifier.predict("path/to/image.png", min_k=1, max_k=3)
```

未顯式指定路徑時，`PonyChartClassifier` 會使用使用者 cache 目錄中的 runtime artifact：

- macOS: `~/Library/Caches/ponychart-classifier/`
- Linux: `~/.cache/ponychart-classifier/` 或 `XDG_CACHE_HOME`
- Windows: `%LOCALAPPDATA%\\ponychart-classifier\\Cache\\`

這些檔案不隨 package 發佈；若 cache 被清掉，下一次 `preload()`、`predict()` 或 `update()` 會重新下載。

## 工作流程

### 1. 收集圖片

將新的 PonyChart 截圖 (PNG) 放入 `rawimage/` 資料夾。

### 2. 安裝訓練依賴

```bash
# 只需一次，標註工具與訓練皆需要
uv pip install -e ".[train]"
```

### 3. 標註圖片

```bash
uv run python -m app.label_images
```

### 4. 訓練模型

```bash

# 執行訓練 (若存在 checkpoint.pt 則自動從上次結果繼續訓練)
uv run python scripts/train.py

# 強制從頭訓練 (忽略 checkpoint，從 ImageNet 預訓練權重開始)
uv run python scripts/train.py --from-scratch
```

訓練完成後會覆寫 `artifacts/model.onnx`、`artifacts/thresholds.json` 和 `checkpoint.pt`。
開發者可直接從 `artifacts/` 上傳模型到雲端；一般使用者的 runtime `update()` 則只更新 user cache dir。

### Resume 訓練

新增圖片並標註後，直接執行 `train.py` 即可。腳本會自動偵測 `checkpoint.pt`：

- **有 checkpoint**: 載入之前的模型權重，跳過 Phase 1 (head-only)，直接進入 Phase 2 fine-tuning，收斂更快
- **無 checkpoint**: 從 ImageNet 預訓練權重開始完整兩階段訓練

### 訓練超參數

所有超參數集中於 `src/ponychart_classifier/training/constants.py`，修改後對所有腳本生效：

| 參數 | 預設值 | 說明 |
| ---- | ------ | ---- |
| `BACKBONE` | `efficientnet_b0` | 見下方支援的 backbone |
| `BATCH_SIZE` | 64 | 批次大小 |
| `SEED` | 42 | 隨機種子 |
| `PHASE1_EPOCHS` | 30 | Phase 1 (head-only) 訓練輪數 |
| `PHASE1_PATIENCE` | 5 | Phase 1 early stopping patience |
| `PHASE2_EPOCHS` | 100 | Phase 2 (full fine-tuning) 最大訓練輪數 |
| `PHASE2_PATIENCE` | 12 | Phase 2 early stopping patience |
| `LR_HEAD` | 4e-3 | Head 層學習率 |
| `LR_FEATURES` | 1.2e-4 | Backbone 特徵提取層學習率 |
| `LR_CLASSIFIER` | 1.2e-3 | 分類器層學習率 |
| `VAL_SIZE` | 0.15 | 驗證集比例 |
| `HOLDOUT_TEST_SIZE` | 0.20 | Holdout 測試集比例 |

## 支援的 Backbone

| Backbone | 參數量 | ONNX 大小 | 說明 |
| -------- | ------ | --------- | ---- |
| `mobilenet_v3_small` | 2.5M | ~4MB | 輕量快速 |
| `mobilenet_v3_large` | 5.4M | ~9MB | 精度最高 |
| `efficientnet_b0` | 5.3M | ~11MB | 預設，精度接近 Large，但訓練較慢 |
| `efficientnet_b2` | 9.1M | ~18MB | 最大模型，較高精度但較慢 |

所有 backbone 都使用 ImageNet 預訓練權重 + transfer learning。
推論端使用 ONNX Runtime，backbone 更換後只需重新匯出 `artifacts/model.onnx`，推論程式碼不需改動。

## CLI 實驗模組

CLI 模組支援 `--run` / `--report` 兩階段：先逐一訓練，再合併比較。
所有實驗使用 `training/constants.py` 中的超參數設定。

```bash
# 比較不同 backbone 的效果
uv run --extra train python -m cli.compare_backbones --run efficientnet_b0
uv run --extra train python -m cli.compare_backbones --report

# 資料增強 ablation study (hflip, vflip, rot15, rot45, rot90, current, none)
uv run --extra train python -m cli.analyze_augmentations --run hflip
uv run --extra train python -m cli.analyze_augmentations --report

# 比較輸入解析度 (以生產 INPUT_SIZE 的倍率表示)
uv run --extra train python -m cli.compare_resolution --run 1.00x
uv run --extra train python -m cli.compare_resolution --report

# 比較正方形 vs 原始長寬比訓練
uv run --extra train python -m cli.compare_aspect_ratio --run square_320
uv run --extra train python -m cli.compare_aspect_ratio --report

# Batch size / LR 超參數搜尋 (Linear Scaling Rule)
uv run --extra train python -m cli.search_batch_lr --run 64
uv run --extra train python -m cli.search_batch_lr --report

# CPU 推論延遲 benchmark (不需 train 依賴)
uv run python -m cli.benchmark_cpu_inference --run efficientnet_b0
uv run python -m cli.benchmark_cpu_inference --report
```

## 分析腳本

獨立腳本，一次執行完成全部分析。

```bash
# 訓練
uv run --extra train python scripts/train.py              # 自動偵測 resume / from-scratch
uv run --extra train python scripts/train.py --from-scratch

# 裁切圖片三組實驗 (原圖+偏差裁切 / 純原圖 / 原圖+平衡裁切)
uv run --extra train python scripts/compare_crops.py

# pos_weight A/B 測試
uv run --extra train python scripts/compare_pos_weight.py

# Resume vs from-scratch 交叉點分析 (校正 RETRAIN_NEW_DATA_RATIO)
uv run --extra train python scripts/compare_resume_scratch.py

# Holdout 評估 (80/20 timestamp split)
uv run --extra train python scripts/evaluate_holdout.py

# Learning curve (power-law 外推，估算增加資料的邊際效益)
uv run --extra train python scripts/learning_curve.py

# DataLoader profiling (找出 data loading vs GPU 瓶頸)
uv run --extra train python scripts/profile_dataloader.py

# 卡牌機率模型擬合 (純數學，不需 train 依賴)
uv run python scripts/fit_cards.py fit 120 45 10
uv run python scripts/fit_cards.py predict 0.35 --n 1000
```

## 模型架構

- **Backbone**: 可選 MobileNetV3-Small/Large 或 EfficientNet-B0/B2 (預設 EfficientNet-B0，ImageNet 預訓練)
- **訓練策略**: Phase 1 head-only + Phase 2 full fine-tuning，支援從 checkpoint 繼續訓練
- **輸出**: 6 個 sigmoid 節點 (多標籤分類)
- **推論引擎**: ONNX Runtime (CPU)
- **推論速度**: 3-21ms / 張
