feat: 임상 Live 6채널 시각화 화면 (ClinicalLiveView)
vesiscan_test Section 2 프로토콜 패턴을 안드로이드 앱 안에서 직접 구현.
실제 몸에 센서 붙인 채 6채널 실시간 그래프 보면서 측정 + 저장 가능.
신규 화면 ClinicalLiveView:
- 600ms 간격 mtb? 자동 송신 (Piezo 6ch + IMU 동시)
- 2×3 grid 라이브 차트 (raw ADC waveform, CH0~CH5 색상별)
· y축 0~4095 (12-bit ADC) 고정 + 가로 grid 4분할
· 각 채널 peak 값 표시
- IMU 카드 — ImuAnalysis 기반 자세 + tilt + shake (흔들림 시 빨강 경고)
- 상단: 세션 라벨 + S=Subject TV=TrueVol Ab=Abdomen 한 줄 + #cycle ✓captured
- 컨트롤: Pause/Resume · Capture (수동 저장) · Auto Capture 토글 · End Session
ClinicalSession 모델 확장:
- subjectId / trueVolumeMl / abdomenThicknessMm 필드 추가
- folderName 에 subjectId prefix (예: "S001_VBT26050202_SUPINE_ALIGN_0CM_...")
- meta.json 에 subject_id / true_volume_ml / abdomen_thickness_mm 자동 기록
ClinicalSessionStore:
- lastSubjectId / lastTrueVolumeMl / lastAbdomenThicknessMm 기억 (다음 측정 자동 채움)
ClinicalHomeView 입력 폼 확장:
- Subject ID 텍스트필드
- True Volume (mL) + Abdomen (mm) 2-컬럼 숫자 입력
- Start Live Measurement 버튼 → CLINICAL_LIVE 화면으로 직행
(이전: alignment/BV step에 따라 PlacementGuide/PiezoMonitoring 분기)
AppState/MainActivity:
- AppScreen.CLINICAL_LIVE 추가
- MainActivity Back: LIVE 시 endMeasurement + ClinicalHome 복귀
데이터 흐름:
Start Live → ClinicalLiveView 진입 → mtb? 600ms loop
→ piezoCollector → 6채널 buffer (자동 또는 수동 capture)
→ AdcCsvLogger.log → adc.csv 누적
→ imuCollector → 15 IMU samples
→ ImuCsvLogger.log → imu.csv 누적
End/Back → endMeasurement → meta.json finalize → ClinicalHome 복귀
장점:
- 검사자가 실제 시연 중 6채널 신호 품질 즉시 확인
- Auto Capture로 매 사이클 자동 저장 (Pause로 일시 멈춤)
- subject_id 기반 폴더명으로 피험자별 grouping 자연스럽게
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,14 +44,22 @@ data class ClinicalSession(
|
||||
val step: ClinicalStep,
|
||||
val examiner: String = "",
|
||||
val notes: String = "",
|
||||
// 임상 시험 정보 (vesiscan_test Section 2-1 프로토콜과 매칭)
|
||||
val subjectId: String = "", // 피험자 ID (예: "S001")
|
||||
val trueVolumeMl: Double? = null, // 외부 초음파 측정기 참값 (mL)
|
||||
val abdomenThicknessMm: Double? = null, // 복부 두께 측정기 참값 (mm)
|
||||
val startedAt: Long = System.currentTimeMillis(),
|
||||
var endedAt: Long? = null
|
||||
) {
|
||||
private val folderFmt = SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US)
|
||||
|
||||
/** "VBT26050202_SUPINE_ALIGN_0CM_2026-05-28_14-30-15" */
|
||||
/** "S001_VBT26050202_SUPINE_ALIGN_0CM_2026-05-28_14-30-15"
|
||||
* subjectId 비어있으면 기존 형식 유지 (deviceName_posture_step_timestamp). */
|
||||
val folderName: String
|
||||
get() = "${deviceName}_${posture.name}_${step.name}_${folderFmt.format(Date(startedAt))}"
|
||||
get() = buildString {
|
||||
if (subjectId.isNotBlank()) { append(subjectId); append('_') }
|
||||
append("${deviceName}_${posture.name}_${step.name}_${folderFmt.format(Date(startedAt))}")
|
||||
}
|
||||
|
||||
/** clinical_label 컬럼/필드 값 (라벨링 자동) */
|
||||
val label: String
|
||||
|
||||
Reference in New Issue
Block a user