# 两级路径下校准文件查找问题修复

## 问题描述

运行 `eval_tools/model_comparison/compare_models_with_common_matches.sh` 时出现警告：

```
Warning: Calibration file not found for case seq-27
```

## 问题原因

在启用两级路径结构（`path_depth: 2`）和 ROI 地面真值处理（`roi_gt.enabled: true`）时，ROI processor 需要加载校准文件来计算 ROI 边界。

原有的校准文件查找逻辑只支持一级路径：
```python
# 原有逻辑（仅支持一级路径）
case_calib_path = self.calib_root / case_name / "calib/L2_calib/camera4.json"
```

对于两级路径结构：
```
calib_root/
    level1_dir/          # 第一级目录（如 G1M3_AFS1616）
        case_name/       # 第二级目录（如 seq-27）
            calib/L2_calib/camera4.json
```

原有逻辑会查找：
```
calib_root/seq-27/calib/L2_calib/camera4.json  ❌ 错误路径
```

正确路径应该是：
```
calib_root/G1M3_AFS1616/seq-27/calib/L2_calib/camera4.json  ✓ 正确路径
```

## 解决方案

### 1. 修改 `evaluator.py`

在 `image_pairs` 中添加 `level1_name` 信息：

```python
self.image_pairs.append({
    'case': case_name,
    'frame': frame_name,
    'det_file': str(det_file),
    'gt_file': str(gt_file),
    'img_width': img_width,
    'img_height': img_height,
    'min_box_size': self.min_box_size,
    'level1_name': level1_name  # 新增：第一级目录名称
})
```

### 2. 修改 `roi_processor.py`

#### 2.1 更新 `load_calibration` 方法

添加 `level1_name` 参数支持：

```python
def load_calibration(self, case_name, frame_name=None, level1_name=None):
    """
    加载 case 的校准参数。

    Args:
        case_name: str, case 标识符
        frame_name: str, 可选的帧名称
        level1_name: str, 可选的第一级目录名称（用于两级路径结构）

    Returns:
        dict 包含校准参数: focal_u, focal_v, cu, cv, yaw, pitch, 等
    """
    if self.calib_root is None:
        return None

    # 缓存键包含 level1_name
    cache_key = f"{level1_name}/{case_name}" if level1_name else f"{case_name}"
    if cache_key in self.calib_cache:
        return self.calib_cache[cache_key]

    # 根据是否有 level1_name 构建不同的路径
    if level1_name:
        # 两级路径: calib_root/level1/case/calib/L2_calib/camera4.json
        case_calib_path = self.calib_root / level1_name / case_name / "calib/L2_calib/camera4.json"
    else:
        # 一级路径: calib_root/case/calib/L2_calib/camera4.json
        case_calib_path = self.calib_root / case_name / "calib/L2_calib/camera4.json"

    # ... 后续处理
```

#### 2.2 更新 `process_case_frame` 方法

添加 `level1_name` 参数：

```python
def process_case_frame(self, case_name, frame_name, annotations, level1_name=None):
    """
    处理特定 case 和帧的标注。

    Args:
        case_name: str, case 标识符
        frame_name: str, 帧标识符
        annotations: list, 来自 GroundTruthParser 的标注
        level1_name: str, 可选的第一级目录名称（用于两级路径结构）

    Returns:
        tuple: (processed_annotations, roi_bounds) 或 (annotations, None) 如果没有 ROI
    """
    if self.roi_config is None:
        return annotations, None

    # 加载校准参数，传入 level1_name
    calib_params = self.load_calibration(case_name, frame_name, level1_name)
    # ...
```

### 3. 更新调用点

在 `evaluator.py` 中调用 `process_case_frame` 时传入 `level1_name`：

```python
# Apply ROI processing to ground truth if enabled
if roi_processor is not None:
    gts, _ = roi_processor.process_case_frame(
        pair['case'],
        pair['frame'],
        gts,
        level1_name=pair.get('level1_name')  # 传入 level1_name
    )
```

## 修改的文件

1. **eval_tools/evaluator/evaluator.py**
   - 在 `load_data_from_paths()` 中添加 `level1_name` 到 `image_pairs`
   - 在调用 `process_case_frame()` 时传入 `level1_name`

2. **eval_tools/evaluator/roi_processor.py**
   - 更新 `load_calibration()` 方法签名和实现
   - 更新 `process_case_frame()` 方法签名和实现

3. **eval_tools/tests/test_roi_processor_2level.py** (新增)
   - 测试脚本验证 ROI processor 在两级路径下的功能

## 测试验证

运行测试脚本：

```bash
python eval_tools/tests/test_roi_processor_2level.py
```

测试结果：
```
============================================================
Test Summary
============================================================
1-level structure: ✓ PASSED
2-level structure: ✓ PASSED

✓ All tests passed!
```

## 向后兼容性

- 对于一级路径结构（`path_depth: 1`），`level1_name` 为 `None`，行为与之前完全相同
- 对于两级路径结构（`path_depth: 2`），`level1_name` 包含第一级目录名称，正确构建校准文件路径
- 所有现有配置和脚本无需修改即可继续工作

## 使用示例

### 配置文件设置

```yaml
dataset:
  det_path: "/data1/dongying/Mono3d/G1M3/CNCAP_results/mono3d/evalset_roi0"
  gt_path: "/mnt/mono3d/xdzhu_data/Mono3d/Mono3d_4face_2m_g1m3/driving_png/G1M3_AFS1616"
  path_depth: 2  # 启用两级路径

roi_gt:
  enabled: true
  calib_root: "/mnt/mono3d/xdzhu_data/Mono3d/Mono3d_4face_2m_g1m3/driving_png/G1M3_AFS1616"
  roi_config: [1920, 960]
```

### 目录结构

```
calib_root (G1M3_AFS1616)/
    ├── level1_dir_A/
    │   ├── seq-27/
    │   │   └── calib/L2_calib/camera4.json  ✓ 现在可以正确找到
    │   └── seq-28/
    │       └── calib/L2_calib/camera4.json
    └── level1_dir_B/
        └── seq-29/
            └── calib/L2_calib/camera4.json
```

## 预期效果

修复后，运行评测脚本时：

1. **不再出现校准文件找不到的警告**（对于存在的校准文件）
2. ROI 处理能够正确加载两级路径下的校准文件
3. 地面真值的 ROI 过滤和裁剪能够正常工作
4. 评测结果更加准确

## 注意事项

1. **校准文件必须存在**: 如果某个 case 确实没有校准文件，仍然会显示警告，但这是正常的
2. **路径结构必须一致**: `calib_root` 的目录结构必须与 `gt_path` 的结构匹配
3. **level1 目录名称必须匹配**: 在两级路径下，第一级目录名称在检测结果、真值和校准文件路径中必须完全相同

## 故障排查

### 问题：仍然提示找不到校准文件

**检查项**:
1. 确认 `path_depth: 2` 已设置
2. 确认 `calib_root` 路径正确
3. 确认校准文件路径为: `calib_root/level1/case/calib/L2_calib/camera4.json`
4. 确认 level1 目录名称在所有路径中一致

**调试方法**:
```python
# 在 roi_processor.py 的 load_calibration 方法中添加调试输出
print(f"Looking for calibration at: {case_calib_path}")
print(f"File exists: {case_calib_path.exists()}")
```

### 问题：某些 case 找到了，某些找不到

**可能原因**:
- 部分 case 的校准文件确实不存在
- 部分 case 的目录结构不一致

**解决方案**:
- 检查缺失的校准文件
- 统一目录结构
- 或者在配置中禁用 ROI GT 处理: `roi_gt.enabled: false`

## 总结

此修复确保了评测系统在两级路径结构下能够正确查找和加载校准文件，使得 ROI 地面真值处理功能能够正常工作。修改保持了向后兼容性，不影响现有的一级路径结构使用。
