# QA 검수 로그 — PancreaClear3D

- **프로젝트:** 2026-06-01-1-pancreaclear3d (DM / 동물실험 도구 / ex vivo 3D 정량)
- **검수일:** 2026-06-01
- **환경:** Python 3, numpy 2.0.2, scipy 1.13.1 / **skimage 미설치, tifffile 미설치**
- **종합 결과:** ✅ PASS (필수 경로 + numpy-only fallback 경로 모두 통과)

---

## 1. 의존성 가용성

| 패키지 | 상태 | 비고 |
|--------|------|------|
| numpy | ✅ 2.0.2 | 필수 |
| scipy / scipy.ndimage | ✅ 1.13.1 | 선택 — 가용 시 watershed 분리 사용 |
| skimage | ❌ 미설치 | 사용 안 함(설계상 import 강제 금지) |
| tifffile | ❌ 미설치 | 선택 — 미설치 시 `--input`이 안내 후 합성 fallback |

> scikit-image가 없으므로 segmentation은 scipy.ndimage(가용 시) 또는 순수 numpy
> fallback으로 동작. 두 경로 모두 검증함.

---

## 2. 검수 항목별 결과

### ✅ T1. 구문 검증
```
$ python3 -c "import ast; ast.parse(open('main.py').read())"
OK   (rc=0)
```

### ✅ T2. `--help` 동작
```
$ python3 main.py --help        # rc=0
usage: main.py [-h] [--demo | --input PATH] [--summary] [--top N]
               [--n-islets N] [--seed SEED] [--proximity-um UM] [--json PATH]
```
모든 서브옵션(--demo/--input/--summary/--top/--n-islets/--seed/--proximity-um/--json)
및 사용 예시 표시 확인.

### ✅ T3. 기본 실행 / `--summary`
- `python3 main.py` rc=0
- `python3 main.py --summary` rc=0
- 대표 출력(seed=7, 결정론적)은 아래 [5. 대표 출력] 참고.
- 검증 포인트:
  - β:α 비율 **3.77 : 1** (생리학적 ~3-4:1 범위) ✅
  - size class가 small~very_large까지 분포(약 4-decade 체적) ✅
  - head/body/tail zonal gradient에서 **tail 우세(26 islets)** ✅
  - 2D 단면 추정 편향 typical |bias| **9~18%**, worst **-49%** → 희소 2D sampling이
    β-mass를 수십 %(≈±20-40% 대역) 편향시킴을 시연 ✅

### ✅ T4. numpy-only fallback (scipy/tifffile import 차단 시뮬레이션)
```
$ python3 /tmp/noscipy.py    # __import__ 후킹으로 scipy·tifffile 차단
[env] numpy=2.0.2  scipy=NO (numpy fallback)  tifffile=no
 segmentation engine : pure-numpy fallback labeling
 ... rc=0
 beta : alpha ratio  : 3.50 : 1
```
- scipy 부재 시 `_label_numpy()`(6-연결 flood-fill labeling) +
  `_approx_distance()`(반복 dilation 근사 거리변환)로 자동 대체.
- rc=0, 모든 섹션 정상 출력, 5개 size class 정상 분포.
- (참고) fallback은 watershed 분리가 없어 맞붙은 islet을 일부 병합 → islet 수가 다소
  적게 나옴(설계상 허용, README 한계에 명시).

### ✅ T5. `--json` 출력 유효성
```
$ python3 main.py --json out.json   # rc=0
$ python3 -c "import json; json.load(open('out.json'))"   → valid json
```
keys: alpha_mass_3d_um3, beta_alpha_ratio, beta_mass_3d_um3, bias, class_counts,
disclaimer, endocrine_threshold, have_scipy, ... (labels 배열은 제외, 직렬화 가능).

### ✅ T6. `--input` graceful 처리 (tifffile 부재)
```
$ python3 main.py --input /tmp/nonexistent.tif --summary
[error] could not load --input: tifffile is not installed; --input is unavailable...
[info] falling back to synthetic demo volume.
 ... (정상 합성 리포트, rc=0)
```
크래시 없이 안내 메시지 출력 후 합성 demo로 진행.

### ✅ T7. 데이터 파일 로드
```
$ python3 -c "import json; d=json.load(open('data/stereology_reference.json'))"
classes: 5 zones: 3
```
size class 5개, zonal 구역 3개 정상 파싱. `main.py`의 `load_reference()`가 이 파일을
읽어 size-class 분류·zonal 맵·근접 임계값에 사용. 파일 누락 시 코드 내장 fallback 존재.

### ✅ T8. argparse 에러 처리
```
$ python3 main.py --bogus     # main.py: error: unrecognized arguments: --bogus (rc!=0)
$ python3 main.py --top 0      # rc=0, Top 표 0행(정상)
```

### ✅ T9. 의학 디스클레이머 이중 게재
- README.md 상단 + main.py 출력 머리/꼬리 양쪽에
  "연구용·참고용 — 임상 진단/의사결정 도구가 아님 / NOT a clinical ... device" 포함 확인.

### ✅ T10. 외부 네트워크 호출 없음
- 코드 전수 확인: socket/requests/urllib/http 등 네트워크 호출 없음. 전부 오프라인.

---

## 3. 재시도 로그
- 1차 실행: β:α 비율 447:1(α mantle 과소), size class가 small에 몰림, 2D 편향 -7%로
  과소 시연 → **3개 결함 식별**.
- 수정: (a) α mantle 두께를 alpha_frac 기반 shell로 계산해 voxel 비율 정상화,
  (b) 반경 log-uniform 범위 r_min=1.3 / r_max=25 vox로 확대해 4-decade·5개 class 분포,
  (c) 2D 편향을 "소수 무작위 단면 외삽" 방식으로 재설계해 typical |bias| 9~20% 시연,
  (d) watershed seed를 gaussian smoothing + 임계 상향으로 over-split 방지.
- 2차 실행: 모든 항목 통과 ✅. (재시도 2회 한도 내 해결)

---

## 4. 의도 부합 점검
- 핵심기능 ①~⑤ 모두 구현·출력 확인.
- 진입점은 의존성 없이 `python3 main.py`로 실행 가능(numpy만으로도 성공).
- 형제 폴더(-2-, -3-) 미접근. 빌드 경로 내에만 기록.
- 추가 구현: `--json` 출력, `--seed`/`--n-islets`/`--proximity-um` 튜닝 옵션,
  argparse 에러 처리 — 모두 사용자 편의용이며 범위 내.

---

## 5. 대표 출력 (`python3 main.py --summary`, seed=7, 결정론적)

```
 [1] Islet objects, beta-cell mass, beta/alpha composition
     islet count            : 44
     total beta-cell volume : 105,633,280 um^3  (3D beta-mass proxy)
     total alpha-cell volume: 27,986,432 um^3
     beta : alpha ratio     : 3.77 : 1
 [2] Islet size-class distribution & head/body/tail zonal map
     size small      :  14  ########................
     size medium     :  20  ###########.............
     size large      :   9  #####...................
     size very_large :   1  #.......................
     zonal distribution (count | volume um^3):
       head :  11 islets   vol=29,518,336
       body :   7 islets   vol=39,494,144
       tail :  26 islets   vol=63,885,312
 [3] mean vascular density 0.174 ; innervated islets 41/44 (93%) ; prox 15.0 um
 [4] 3D ground-truth beta volume: 105,990,656 um^3
     3 random 2D sections  -> typ.|bias| 18% (+/-23%, worst -49%)
     12 random 2D sections -> typ.|bias|  9% (+/-11%, worst -26%)
     => sparse 2D sampling typically biases beta-mass by ~9-18% vs 3D ground truth.
```
```
[env] numpy=2.0.2  scipy=yes  tifffile=no   (stderr)
```
