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:
@@ -34,14 +34,20 @@ object ClinicalSessionStore {
|
||||
var lastFinalizedFolder: File? = null
|
||||
private set
|
||||
|
||||
/** 다음 측정 시 기본값으로 자동 채울 사용자 입력 (examiner/notes) */
|
||||
/** 다음 측정 시 기본값으로 자동 채울 사용자 입력 — 같은 피험자/검사자 여러 측정 시 편의 */
|
||||
var lastExaminer: String = ""
|
||||
var lastNotes: String = ""
|
||||
var lastSubjectId: String = ""
|
||||
var lastTrueVolumeMl: String = "" // 문자열로 보관 (입력 폼과 동일 형식)
|
||||
var lastAbdomenThicknessMm: String = ""
|
||||
|
||||
fun startMeasurement(context: Context, session: ClinicalSession) {
|
||||
currentSession = session
|
||||
lastExaminer = session.examiner
|
||||
lastNotes = session.notes
|
||||
lastSubjectId = session.subjectId
|
||||
lastTrueVolumeMl = session.trueVolumeMl?.toString() ?: ""
|
||||
lastAbdomenThicknessMm = session.abdomenThicknessMm?.toString() ?: ""
|
||||
currentLogDir()?.mkdirs()
|
||||
writeMeta(context)
|
||||
}
|
||||
@@ -68,6 +74,10 @@ object ClinicalSessionStore {
|
||||
put("examiner", s.examiner)
|
||||
put("notes", s.notes)
|
||||
put("label", s.label)
|
||||
// 임상 시험 참값 (외부 측정기, vesiscan_test 프로토콜)
|
||||
if (s.subjectId.isNotBlank()) put("subject_id", s.subjectId)
|
||||
s.trueVolumeMl?.let { put("true_volume_ml", it) }
|
||||
s.abdomenThicknessMm?.let { put("abdomen_thickness_mm", it) }
|
||||
put("started_at", s.startedAt)
|
||||
put("started_at_iso",
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.US).format(Date(s.startedAt)))
|
||||
|
||||
Reference in New Issue
Block a user