Skip to main content

cumulus3d_cli/
events.rs

1//! 파이프라인 이벤트. 새 프레임 묶음이 처리될 때마다 단계별로 분해된 결과를 내보낸다.
2//! 이 파일은 세 모듈(session·pipeline·sinks)의 공용 계약이다. 필드 추가는 가능, 기존 필드 변경·삭제는 금지.
3
4use cumulus3d_core::io::ply::PointCloud;
5use cumulus3d_core::{ImageId, Reconstruction, Rigid3, Sim3};
6use std::sync::Arc;
7use std::time::{Duration, SystemTime};
8
9/// 모든 이벤트에 붙는 머리말.
10#[derive(Clone, Debug)]
11pub struct Meta {
12    /// 세션 버전(이벤트를 낸 상태의 단조 증가 번호).
13    pub version: u64,
14    /// 발생 시각.
15    pub at: SystemTime,
16}
17
18/// 구역 번호와 위치 범위 [lo, hi).
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct ZoneRange {
21    /// 구역 번호.
22    pub zone: usize,
23    /// 첫 위치(포함).
24    pub lo: usize,
25    /// 끝 위치(제외).
26    pub hi: usize,
27}
28
29/// 파이프라인 이벤트(단계별 결과). 모든 변형에 [`Meta`] 가 붙는다.
30#[derive(Clone, Debug)]
31pub enum Event {
32    /// 위치 하나의 프레임 묶음이 들어옴.
33    FrameIngested {
34        /// 공통 머리말.
35        meta: Meta,
36        /// 위치 번호.
37        position: usize,
38        /// 이 위치의 영상 이름들.
39        images: Vec<String>,
40    },
41    /// 세션 시작(첫 입력 때 한 번). `positions` = 알려진 전체 위치 수. (session 추가)
42    Started {
43        /// 공통 머리말.
44        meta: Meta,
45        /// 알려진 전체 위치 수(모르면 None).
46        positions: Option<usize>,
47    },
48    /// run.log 한 줄. `stamped` 이면 `[HH:MM:SS] ` 머리(meta.at 현지 시각)를 붙여 쓴다. (session 추가)
49    Log {
50        /// 공통 머리말.
51        meta: Meta,
52        /// 기록할 줄.
53        line: String,
54        /// `[HH:MM:SS] ` 머리를 붙일지.
55        stamped: bool,
56    },
57    /// 영상 하나의 특징 추출 완료.
58    FeaturesExtracted {
59        /// 공통 머리말.
60        meta: Meta,
61        /// 영상 이름.
62        image: String,
63        /// 추출한 특징 수.
64        count: usize,
65        /// 위치 번호.
66        position: usize,
67        /// 영상 id.
68        image_id: ImageId,
69    },
70    /// 새 짝 매칭 결과.
71    /// `pairs` = 이번 위치에서 매칭한 짝 수, `verified` = 유효 기하 짝 수, `matched` = 기술자 매칭을 수행한 짝 수.
72    PairsMatched {
73        /// 공통 머리말.
74        meta: Meta,
75        /// 이번 위치에서 매칭한 짝 수.
76        pairs: usize,
77        /// 유효 기하 짝 수.
78        verified: usize,
79        /// 위치 번호.
80        position: usize,
81        /// 기술자 매칭을 수행한 짝 수.
82        matched: usize,
83    },
84    /// 첫 모델이 만들어짐.
85    ModelInitialized {
86        /// 공통 머리말.
87        meta: Meta,
88        /// 등록된 영상 수.
89        registered: usize,
90        /// 위치 번호.
91        position: usize,
92        /// 관련 모델 사본.
93        model: Arc<Reconstruction>,
94    },
95    /// 영상이 모델에 등록됨.
96    FrameRegistered {
97        /// 공통 머리말.
98        meta: Meta,
99        /// 위치 번호.
100        position: usize,
101        /// 영상 이름.
102        image: String,
103        /// 영상 id.
104        image_id: ImageId,
105        /// 등록된 자세(world → 카메라).
106        cam_from_world: Rigid3,
107    },
108    /// 위치 하나의 등록·삼각측량이 끝남.
109    PositionDone {
110        /// 공통 머리말.
111        meta: Meta,
112        /// 위치 번호.
113        position: usize,
114        /// 등록된 영상 수.
115        registered: usize,
116        /// 이 위치까지 기대한 영상 수.
117        expected: usize,
118    },
119    /// 구역의 마지막 위치가 도착함.
120    /// `model` = 그 시점 체인 모델 사본(정렬 전).
121    ZoneArrived {
122        /// 공통 머리말.
123        meta: Meta,
124        /// 구역 범위.
125        range: ZoneRange,
126        /// 관련 모델 사본.
127        model: Arc<Reconstruction>,
128    },
129    /// 구역 초벌 점군(BA 없음).
130    /// `model` = 초벌 모델(구역 0 은 GPS 정렬본), `dense` = 거짓이면 조밀화를 하지 않았거나 실패해 `cloud` 가 빈 자리표시.
131    /// `frame` = 점군 좌표계("gps" | "refined_K" | "unaligned").
132    ZonePreview {
133        /// 공통 머리말.
134        meta: Meta,
135        /// 구역 범위.
136        range: ZoneRange,
137        /// 점군(조밀화가 없으면 빈 자리표시).
138        cloud: Arc<PointCloud>,
139        /// 관련 모델 사본.
140        model: Arc<Reconstruction>,
141        /// 조밀화를 실제로 했는지.
142        dense: bool,
143        /// 점군 좌표계("gps" | "refined_K" | "unaligned").
144        frame: String,
145    },
146    /// 정밀 작업 시작(배경). (session 추가)
147    RefineStarted {
148        /// 공통 머리말.
149        meta: Meta,
150        /// 구역 번호.
151        zone: usize,
152    },
153    /// 정밀 작업의 BA 직후 모델(GPS 정렬 전). (session 추가)
154    ZoneAdjusted {
155        /// 공통 머리말.
156        meta: Meta,
157        /// 구역 번호.
158        zone: usize,
159        /// 관련 모델 사본.
160        model: Arc<Reconstruction>,
161    },
162    /// 구역 정밀 자세 확정(BA + GPS 정렬).
163    /// `model` = BA + GPS 정렬된 정밀 모델.
164    ZoneRefinedPose {
165        /// 공통 머리말.
166        meta: Meta,
167        /// 구역 범위.
168        range: ZoneRange,
169        /// 등록된 영상 수.
170        registered: usize,
171        /// 평균 재투영 오차(픽셀).
172        mean_reproj_px: f64,
173        /// 관련 모델 사본.
174        model: Arc<Reconstruction>,
175    },
176    /// 구역 정밀 점군. 같은 구역 초벌을 대체한다.
177    /// `dense` = 거짓이면 `cloud` 는 빈 자리표시.
178    ZoneRefined {
179        /// 공통 머리말.
180        meta: Meta,
181        /// 구역 범위.
182        range: ZoneRange,
183        /// 점군(조밀화가 없으면 빈 자리표시).
184        cloud: Arc<PointCloud>,
185        /// 관련 모델 사본.
186        model: Arc<Reconstruction>,
187        /// 조밀화를 실제로 했는지.
188        dense: bool,
189    },
190    /// 정밀 모델이 다음 등록의 기준으로 채택됨.
191    BaseAdopted {
192        /// 공통 머리말.
193        meta: Meta,
194        /// 구역 번호.
195        zone: usize,
196    },
197    /// 이미 보낸 구역의 좌표계 변환(점군은 다시 보내지 않음): new_from_old.
198    Reanchored {
199        /// 공통 머리말.
200        meta: Meta,
201        /// 구역 번호.
202        zone: usize,
203        /// new_from_old 변환.
204        transform: Sim3,
205    },
206    /// 화면용 스냅샷(사건 순서 번호와 합성 점군).
207    Snapshot {
208        /// 공통 머리말.
209        meta: Meta,
210        /// 사건 순서 번호.
211        index: usize,
212        /// 점군(조밀화가 없으면 빈 자리표시).
213        cloud: Arc<PointCloud>,
214    },
215    /// 모든 위치 처리 완료.
216    AllPositionsDone {
217        /// 공통 머리말.
218        meta: Meta,
219    },
220    /// 모든 정밀 작업 완료.
221    /// `chain` = 최종 체인 모델.
222    AllRefinedDone {
223        /// 공통 머리말.
224        meta: Meta,
225        /// 최종 체인 모델.
226        chain: Option<Arc<Reconstruction>>,
227    },
228    /// 세션 종료 요약(모든 이벤트의 마지막). `stage_times` = (단계, 누적 시간, 횟수) 단계 이름순,
229    /// `pipeline_secs` = 시작 ~ 모든 정밀 완료, `chain_stats` = 최종 체인 모델 통계 줄. (session 추가)
230    Finished {
231        /// 공통 머리말.
232        meta: Meta,
233        /// 시작 ~ 모든 정밀 완료(초).
234        pipeline_secs: f64,
235        /// (단계, 누적 시간, 횟수) 단계 이름순.
236        stage_times: Vec<(String, Duration, usize)>,
237        /// 최종 체인 모델 통계 줄.
238        chain_stats: Vec<String>,
239    },
240    /// `Command::ResetFrom` 처리됨. (session 추가)
241    Reset {
242        /// 공통 머리말.
243        meta: Meta,
244        /// 위치 번호.
245        position: usize,
246    },
247    /// `Command::InvalidateZone` 처리됨. (session 추가)
248    ZoneInvalidated {
249        /// 공통 머리말.
250        meta: Meta,
251        /// 구역 번호.
252        zone: usize,
253    },
254    /// 경고.
255    Warning {
256        /// 공통 머리말.
257        meta: Meta,
258        /// 메시지.
259        message: String,
260    },
261    /// 오류(훅 패닉 포함).
262    Error {
263        /// 공통 머리말.
264        meta: Meta,
265        /// 메시지.
266        message: String,
267    },
268}
269
270/// 이벤트 종류(훅 등록·필터링용).
271#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
272pub enum EventKind {
273    /// [`Event::FrameIngested`].
274    FrameIngested,
275    /// [`Event::Started`].
276    Started,
277    /// [`Event::Log`].
278    Log,
279    /// [`Event::RefineStarted`].
280    RefineStarted,
281    /// [`Event::ZoneAdjusted`].
282    ZoneAdjusted,
283    /// [`Event::Finished`].
284    Finished,
285    /// [`Event::Reset`].
286    Reset,
287    /// [`Event::ZoneInvalidated`].
288    ZoneInvalidated,
289    /// [`Event::FeaturesExtracted`].
290    FeaturesExtracted,
291    /// [`Event::PairsMatched`].
292    PairsMatched,
293    /// [`Event::ModelInitialized`].
294    ModelInitialized,
295    /// [`Event::FrameRegistered`].
296    FrameRegistered,
297    /// [`Event::PositionDone`].
298    PositionDone,
299    /// [`Event::ZoneArrived`].
300    ZoneArrived,
301    /// [`Event::ZonePreview`].
302    ZonePreview,
303    /// [`Event::ZoneRefinedPose`].
304    ZoneRefinedPose,
305    /// [`Event::ZoneRefined`].
306    ZoneRefined,
307    /// [`Event::BaseAdopted`].
308    BaseAdopted,
309    /// [`Event::Reanchored`].
310    Reanchored,
311    /// [`Event::Snapshot`].
312    Snapshot,
313    /// [`Event::AllPositionsDone`].
314    AllPositionsDone,
315    /// [`Event::AllRefinedDone`].
316    AllRefinedDone,
317    /// [`Event::Warning`].
318    Warning,
319    /// [`Event::Error`].
320    Error,
321}
322
323impl Event {
324    /// 이벤트 종류.
325    pub fn kind(&self) -> EventKind {
326        use Event::*;
327        match self {
328            FrameIngested { .. } => EventKind::FrameIngested,
329            Started { .. } => EventKind::Started,
330            Log { .. } => EventKind::Log,
331            RefineStarted { .. } => EventKind::RefineStarted,
332            ZoneAdjusted { .. } => EventKind::ZoneAdjusted,
333            Finished { .. } => EventKind::Finished,
334            Reset { .. } => EventKind::Reset,
335            ZoneInvalidated { .. } => EventKind::ZoneInvalidated,
336            FeaturesExtracted { .. } => EventKind::FeaturesExtracted,
337            PairsMatched { .. } => EventKind::PairsMatched,
338            ModelInitialized { .. } => EventKind::ModelInitialized,
339            FrameRegistered { .. } => EventKind::FrameRegistered,
340            PositionDone { .. } => EventKind::PositionDone,
341            ZoneArrived { .. } => EventKind::ZoneArrived,
342            ZonePreview { .. } => EventKind::ZonePreview,
343            ZoneRefinedPose { .. } => EventKind::ZoneRefinedPose,
344            ZoneRefined { .. } => EventKind::ZoneRefined,
345            BaseAdopted { .. } => EventKind::BaseAdopted,
346            Reanchored { .. } => EventKind::Reanchored,
347            Snapshot { .. } => EventKind::Snapshot,
348            AllPositionsDone { .. } => EventKind::AllPositionsDone,
349            AllRefinedDone { .. } => EventKind::AllRefinedDone,
350            Warning { .. } => EventKind::Warning,
351            Error { .. } => EventKind::Error,
352        }
353    }
354    /// 공통 머리말.
355    pub fn meta(&self) -> &Meta {
356        use Event::*;
357        match self {
358            FrameIngested { meta, .. } | FeaturesExtracted { meta, .. } | PairsMatched { meta, .. } | ModelInitialized { meta, .. }
359            | FrameRegistered { meta, .. } | PositionDone { meta, .. } | ZoneArrived { meta, .. } | ZonePreview { meta, .. }
360            | ZoneRefinedPose { meta, .. } | ZoneRefined { meta, .. } | BaseAdopted { meta, .. } | Reanchored { meta, .. }
361            | Snapshot { meta, .. } | AllPositionsDone { meta } | AllRefinedDone { meta, .. }
362            | Started { meta, .. } | Log { meta, .. } | RefineStarted { meta, .. } | ZoneAdjusted { meta, .. } | Finished { meta, .. }
363            | Reset { meta, .. } | ZoneInvalidated { meta, .. } | Warning { meta, .. } | Error { meta, .. } => meta,
364        }
365    }
366}
367
368impl Event {
369    /// timeline.txt 의 사건 문구(없으면 None). 문구·형식은 기존 기록과 같다.
370    pub fn timeline_text(&self) -> Option<String> {
371        use Event::*;
372        Some(match self {
373            Started { positions, .. } => match positions {
374                Some(n) => format!("start NPOS={n}"),
375                None => "start".to_string(),
376            },
377            ModelInitialized { registered, .. } => format!("init_model Registered images: {registered}"),
378            BaseAdopted { zone, .. } => format!("adopt refined {zone} as base"),
379            PositionDone { position, registered, expected, .. } => format!("pos {position} registered {registered}/{expected}"),
380            ZoneArrived { range, .. } => format!("region {} arrived pos[{},{})", range.zone, range.lo, range.hi),
381            ZonePreview { range, .. } => format!("preview {} ready", range.zone),
382            RefineStarted { zone, .. } => format!("refined {zone} ba_start"),
383            ZoneRefinedPose { range, registered, mean_reproj_px, .. } => format!(
384                "refined {} pose_ready Registered images: {registered} Mean reprojection error: {mean_reproj_px:.6}px ",
385                range.zone
386            ),
387            ZoneRefined { range, .. } => format!("refined {} ready", range.zone),
388            AllPositionsDone { .. } => "all positions done".to_string(),
389            AllRefinedDone { .. } => "all refined done".to_string(),
390            _ => return None,
391        })
392    }
393}
394
395/// 리듀서에 넣는 명령.
396#[derive(Clone, Debug)]
397pub enum Command {
398    /// 이 위치부터 다시 처리(그 이후 상태 폐기).
399    ResetFrom(usize),
400    /// 구역 결과를 무효화하고 다시 만들게 함.
401    InvalidateZone(usize),
402}