1use libflate::{finish::AutoFinishUnchecked, gzip::Encoder};
5use std::{io::Write, sync::Arc};
6use tracing::info;
7
8use braid_types::{
9 BRAID_SCHEMA, CAM_SETTINGS_DIRNAME, CamInfoRow, FEATURE_DETECT_SETTINGS_DIRNAME,
10 IMAGES_DIRNAME, MyFloat, RECONSTRUCT_LATENCY_HLOG_FNAME, REPROJECTION_DIST_HLOG_FNAME,
11 TextlogRow, TrackingParams,
12};
13
14use braidz_types::BraidMetadata;
15
16use crate::{
17 ConnectedCamerasManager, ExperimentInfoRow, FrameDataAndPoints, HistogramWritingState,
18 KalmanEstimateRecord, OrderingWriter, Result, SaveToDiskMsg, StartSavingCsvConfig,
19 TrackingParamsSaver, finish_histogram, histogram_record, save_hlog,
20};
21
22struct WritingState {
23 output_dirname: std::path::PathBuf,
24 readme_fd: Option<std::fs::File>,
33 save_empty_data2d: bool,
34 kalman_estimates_wtr: Option<OrderingWriter>,
36 data_assoc_wtr: Option<csv::Writer<Box<dyn std::io::Write + Send>>>,
37 data_2d_wtr: csv::Writer<Box<dyn std::io::Write + Send>>,
38 textlog_wtr: csv::Writer<Box<dyn std::io::Write + Send>>,
39 trigger_clock_info_wtr: csv::Writer<Box<dyn std::io::Write + Send>>,
40 experiment_info_wtr: csv::Writer<Box<dyn std::io::Write + Send>>,
41 writer_stats: Option<(usize, usize)>,
42 file_start_time: std::time::SystemTime,
43
44 reconstruction_latency_usec: Option<HistogramWritingState>,
45 reproj_dist_pixels: Option<HistogramWritingState>,
46 last_flush: std::time::Instant,
47}
48
49fn _test_writing_state_is_send() {
50 fn implements<T: Send>() {}
52 implements::<WritingState>();
53}
54
55#[derive(Clone, Debug)]
56pub enum BraidMetadataBuilder {
57 GenerateNew(MetadataParts),
58 Existing(BraidMetadata),
59}
60
61impl BraidMetadataBuilder {
62 pub fn saving_program_name<S: Into<String>>(saving_program_name: S) -> BraidMetadataBuilder {
64 BraidMetadataBuilder::GenerateNew(MetadataParts {
65 saving_program_name: saving_program_name.into(),
66 })
67 }
68}
69
70#[derive(Clone, Debug)]
71pub struct MetadataParts {
72 saving_program_name: String,
73}
74
75impl WritingState {
76 fn new(
77 cfg: StartSavingCsvConfig,
78 cam_info_rows: Vec<CamInfoRow>,
79 recon: &Option<flydra_mvg::FlydraMultiCameraSystem<MyFloat>>,
80 tracking_params: Arc<TrackingParams>,
81 save_empty_data2d: bool,
82 metadata_builder: BraidMetadataBuilder,
83 ) -> Result<Self> {
84 let output_dirname = cfg.out_dir;
85 let local = cfg.local;
86 let git_revision = cfg.git_rev;
87 let fps = cfg.fps;
88 let per_cam_data = cfg.per_cam_data;
89
90 std::fs::create_dir_all(&output_dirname)?;
94
95 let readme_fd = {
99 let readme_path = output_dirname.join(braid_types::README_MD_FNAME);
100
101 let mut fd = std::fs::File::create(readme_path)?;
102
103 fd.write_all(
106 "\n\nThis is data saved by the braid program. \
107 See https://strawlab.org/braid for more information.\n\n"
108 .as_bytes(),
109 )
110 .unwrap();
111 Some(fd)
112 };
113
114 {
115 let braid_metadata_path = output_dirname.join(braid_types::BRAID_METADATA_YML_FNAME);
116
117 let metadata = match metadata_builder {
118 BraidMetadataBuilder::GenerateNew(parts) => {
119 BraidMetadata {
120 schema: BRAID_SCHEMA, git_revision: git_revision.clone(),
122 original_recording_time: local,
123 save_empty_data2d,
124 saving_program_name: parts.saving_program_name,
125 }
126 }
127 BraidMetadataBuilder::Existing(metadata) => metadata,
128 };
129 let metadata_buf = serde_yaml::to_string(&metadata).unwrap();
130
131 let mut fd = std::fs::File::create(braid_metadata_path)?;
132 fd.write_all(metadata_buf.as_bytes()).unwrap();
133 }
134
135 {
137 let mut image_path = output_dirname.clone();
138 image_path.push(IMAGES_DIRNAME);
139 std::fs::create_dir_all(&image_path)?;
140
141 for (raw_cam_name, data) in per_cam_data.iter() {
142 let buf = data.current_image_png.as_slice();
143 let fname = format!("{}.png", raw_cam_name.as_str());
144 let fullpath = image_path.clone().join(fname);
145 let mut fd = std::fs::File::create(&fullpath)?;
146 fd.write_all(buf)?;
147 }
148 }
149
150 {
152 let mut cam_settings_path = output_dirname.clone();
153 cam_settings_path.push(CAM_SETTINGS_DIRNAME);
154 if per_cam_data
155 .iter()
156 .any(|(_, x)| x.cam_settings_data.is_some())
157 {
158 std::fs::create_dir_all(&cam_settings_path)?;
159 }
160
161 for (raw_cam_name, cam) in per_cam_data.iter() {
162 if let Some(data) = &cam.cam_settings_data {
163 let fname = format!(
164 "{}.{}",
165 raw_cam_name.as_str(),
166 data.current_cam_settings_extension
167 );
168 let fullpath = cam_settings_path.clone().join(fname);
169 let mut fd = std::fs::File::create(&fullpath)?;
170 fd.write_all(data.current_cam_settings_buf.as_bytes())?;
171 }
172 }
173 }
174
175 {
177 let mut feature_detect_settings_path = output_dirname.clone();
178 feature_detect_settings_path.push(FEATURE_DETECT_SETTINGS_DIRNAME);
179 if per_cam_data
180 .iter()
181 .any(|(_, x)| x.feature_detect_settings.is_some())
182 {
183 std::fs::create_dir_all(&feature_detect_settings_path)?;
184 }
185
186 for (raw_cam_name, cam) in per_cam_data.iter() {
187 if let Some(data) = &cam.feature_detect_settings {
188 let buf = toml::to_vec(&data.current_feature_detect_settings)?;
189 let fname = format!("{}.toml", raw_cam_name.as_str());
190 let fullpath = feature_detect_settings_path.join(fname);
191 let mut fd = std::fs::File::create(&fullpath)?;
192 fd.write_all(&buf)?;
193 }
194 }
195 }
196
197 {
199 let mut csv_path = output_dirname.clone();
200 csv_path.push(format!("{}.gz", braid_types::CAM_INFO_CSV_FNAME));
201 let fd = std::fs::File::create(&csv_path)?;
202 let fd: Box<dyn std::io::Write + Send> =
203 Box::new(AutoFinishUnchecked::new(Encoder::new(fd)?));
204 let mut cam_info_wtr = csv::Writer::from_writer(fd);
205 for row in cam_info_rows.iter() {
206 cam_info_wtr.serialize(row)?;
207 }
208 }
209
210 if let Some(recon) = recon {
212 let mut cal_path = output_dirname.clone();
213 cal_path.push(braid_types::CALIBRATION_XML_FNAME);
214 let fd = std::fs::File::create(&cal_path)?;
215 recon.to_flydra_xml(fd)?;
216 }
217
218 let textlog_wtr = {
220 let local_datetime = chrono::Local::now();
221 let mainbrain_timestamp = strand_datetime_conversion::datetime_to_f64(&local_datetime);
222 let (tzname_str, tzname) = match iana_time_zone::get_timezone() {
223 Ok(tzname) => ("time_tzname0", tzname),
224 Err(_err) => {
225 tracing::debug!("Could not get timezone, using UTC offset instead.");
226 let offset = local_datetime.offset();
227 use chrono::offset::Offset;
228 let offset_secs = offset.fix().local_minus_utc();
229 ("UTC_offset_secs", format!("{offset_secs}"))
230 }
231 };
232
233 let fps = match fps {
234 Some(fps) => format!("{fps}"),
235 None => "unknown".to_string(),
236 };
237 let version = "2.0.0";
238 let message = format!(
239 "MainBrain running at {fps} fps, (\
240 flydra_version {version}, git_revision {git_revision}, {tzname_str} {tzname})",
241 );
242
243 let tps = TrackingParamsSaver {
244 tracking_params: (*tracking_params).clone(),
245 git_revision,
246 };
247 let message2 = serde_json::to_string(&tps)?;
248
249 let textlog: Vec<TextlogRow> = vec![
250 TextlogRow {
251 mainbrain_timestamp,
252 cam_id: "mainbrain".to_string(),
253 host_timestamp: mainbrain_timestamp,
254 message,
255 },
256 TextlogRow {
257 mainbrain_timestamp,
258 cam_id: "mainbrain".to_string(),
259 host_timestamp: mainbrain_timestamp,
260 message: message2,
261 },
262 ];
263
264 let mut csv_path = output_dirname.clone();
267 csv_path.push(braid_types::TEXTLOG_CSV_FNAME);
268 let fd = std::fs::File::create(&csv_path)?;
269 let mut textlog_wtr =
270 csv::Writer::from_writer(Box::new(fd) as Box<dyn std::io::Write + Send>);
271 for row in textlog.iter() {
272 textlog_wtr.serialize(row)?;
273 }
274 textlog_wtr.flush()?;
276 textlog_wtr
277 };
278
279 let kalman_estimates_wtr = if let Some(_recon) = recon {
281 let mut csv_path = output_dirname.clone();
282 csv_path.push(format!("{}.gz", braid_types::KALMAN_ESTIMATES_CSV_FNAME));
283 let fd = std::fs::File::create(&csv_path)?;
284 let fd: Box<dyn std::io::Write + Send> =
285 Box::new(AutoFinishUnchecked::new(Encoder::new(fd)?));
286 Some(OrderingWriter::new(csv::Writer::from_writer(fd)))
287 } else {
288 None
289 };
290
291 let trigger_clock_info_wtr = {
292 let mut csv_path = output_dirname.clone();
293 csv_path.push(format!("{}.gz", braid_types::TRIGGER_CLOCK_INFO_CSV_FNAME));
294 let fd = std::fs::File::create(&csv_path)?;
295 let fd: Box<dyn std::io::Write + Send> =
296 Box::new(AutoFinishUnchecked::new(Encoder::new(fd)?));
297 csv::Writer::from_writer(fd)
298 };
299
300 let experiment_info_wtr = {
301 let mut csv_path = output_dirname.clone();
304 csv_path.push(braid_types::EXPERIMENT_INFO_CSV_FNAME);
305 let fd = std::fs::File::create(&csv_path)?;
306 csv::Writer::from_writer(Box::new(fd) as Box<dyn std::io::Write + Send>)
307 };
308
309 let data_assoc_wtr = if let Some(_recon) = recon {
310 let mut csv_path = output_dirname.clone();
311 csv_path.push(format!("{}.gz", braid_types::DATA_ASSOCIATE_CSV_FNAME));
312 let fd = std::fs::File::create(&csv_path)?;
313 let fd: Box<dyn std::io::Write + Send> =
314 Box::new(AutoFinishUnchecked::new(Encoder::new(fd)?));
315 Some(csv::Writer::from_writer(fd))
316 } else {
317 None
318 };
319
320 let data_2d_wtr = {
321 let mut csv_path = output_dirname.clone();
322 csv_path.push(format!("{}.gz", braid_types::DATA2D_DISTORTED_CSV_FNAME));
323 let fd = std::fs::File::create(&csv_path)?;
324 let fd: Box<dyn std::io::Write + Send> =
325 Box::new(AutoFinishUnchecked::new(Encoder::new(fd)?));
326 csv::Writer::from_writer(fd)
327 };
328
329 let writer_stats = if cfg.print_stats { Some((0, 0)) } else { None };
330
331 let file_start_time = if let Some(local) = local {
332 local.into()
333 } else {
334 std::time::SystemTime::now()
335 };
336
337 let (reconstruction_latency_usec, reproj_dist_pixels) = if cfg.save_performance_histograms {
338 (
339 Some(HistogramWritingState::default()),
340 Some(HistogramWritingState::default()),
341 )
342 } else {
343 (None, None)
344 };
345
346 Ok(Self {
347 output_dirname,
348 readme_fd,
349 save_empty_data2d,
350 kalman_estimates_wtr,
351 data_assoc_wtr,
352 data_2d_wtr,
353 textlog_wtr,
354 trigger_clock_info_wtr,
355 experiment_info_wtr,
356 writer_stats,
357 file_start_time,
358 reconstruction_latency_usec,
359 reproj_dist_pixels,
360 last_flush: std::time::Instant::now(),
361 })
362 }
363
364 fn save_data_2d_distorted(&mut self, fdp: FrameDataAndPoints) -> Result<usize> {
365 let data2d_distorted = fdp.into_save(self.save_empty_data2d);
366 for row in data2d_distorted.iter() {
367 self.data_2d_wtr.serialize(row)?;
368 }
369 Ok(data2d_distorted.len())
370 }
371
372 fn flush_all(&mut self) -> Result<()> {
373 if let Some(ref mut kew) = self.kalman_estimates_wtr {
374 kew.flush()?;
375 }
376 if let Some(ref mut daw) = self.data_assoc_wtr {
377 daw.flush()?;
378 }
379 self.data_2d_wtr.flush()?;
380 self.textlog_wtr.flush()?;
381 self.trigger_clock_info_wtr.flush()?;
382 self.experiment_info_wtr.flush()?;
383 self.last_flush = std::time::Instant::now();
384 Ok(())
385 }
386}
387
388impl Drop for WritingState {
389 fn drop(&mut self) {
390 tracing::debug!("WritingState is being dropped, flushing all data to disk.");
391 fn dummy_csv() -> csv::Writer<Box<dyn std::io::Write + Send>> {
392 let fd = Box::new(Vec::with_capacity(0));
393 csv::Writer::from_writer(fd)
394 }
395
396 if let Some(count) = self.writer_stats {
397 info!(
398 " {} rows of 2d detections, {} rows of kalman estimates",
399 count.0, count.1
400 );
401 }
402
403 {
405 self.kalman_estimates_wtr.take();
406 self.data_assoc_wtr.take();
407 self.data_2d_wtr = dummy_csv();
409 self.textlog_wtr = dummy_csv();
410 self.trigger_clock_info_wtr = dummy_csv();
411 self.experiment_info_wtr = dummy_csv();
412 }
413
414 let output_dirname = std::mem::take(&mut self.output_dirname);
417
418 let now_system = std::time::SystemTime::now();
419 {
420 if let Some(reconstruction_latency_usec) = &mut self.reconstruction_latency_usec {
421 finish_histogram(
422 &mut reconstruction_latency_usec.current_store,
423 self.file_start_time,
424 &mut reconstruction_latency_usec.histograms,
425 now_system,
426 )
427 .unwrap();
428
429 save_hlog(
430 &output_dirname,
431 RECONSTRUCT_LATENCY_HLOG_FNAME,
432 &reconstruction_latency_usec.histograms,
433 self.file_start_time,
434 );
435 }
436
437 if let Some(reproj_dist_pixels) = &mut self.reproj_dist_pixels {
438 finish_histogram(
439 &mut reproj_dist_pixels.current_store,
440 self.file_start_time,
441 &mut reproj_dist_pixels.histograms,
442 now_system,
443 )
444 .unwrap();
445
446 save_hlog(
447 &output_dirname,
448 REPROJECTION_DIST_HLOG_FNAME,
449 &reproj_dist_pixels.histograms,
450 self.file_start_time,
451 );
452 }
453 }
454
455 {
457 let replace_extension = match output_dirname.extension() {
465 Some(ext) => ext == "braid",
466 None => false,
467 };
468
469 let output_zipfile: std::path::PathBuf = if replace_extension {
471 output_dirname.with_extension("braidz")
472 } else {
473 let mut tmp = output_dirname.clone().into_os_string();
474 tmp.push(".braidz");
475 tmp.into()
476 };
477
478 info!("creating zip file {}", output_zipfile.display());
479 braidz_writer::dir_to_braidz(&output_dirname, output_zipfile).unwrap();
480
481 self.readme_fd = None;
486
487 info!(
490 "done creating zip file, removing {}",
491 output_dirname.display()
492 );
493 match std::fs::remove_dir_all(&output_dirname) {
494 Ok(()) => {}
495 Err(err) => {
496 panic!(
497 "Error removing original directory {}: {}",
498 output_dirname.display(),
499 err
500 );
501 }
502 }
503 }
504 tracing::debug!("Done writing braidz data to disk.");
505 }
506}
507
508#[tracing::instrument(level = "debug", skip_all)]
514pub(crate) fn writer_task_main(
515 mut braidz_write_rx: tokio::sync::mpsc::Receiver<SaveToDiskMsg>,
516 cam_manager: ConnectedCamerasManager,
517 recon: Option<flydra_mvg::FlydraMultiCameraSystem<MyFloat>>,
518 tracking_params: Arc<TrackingParams>,
519 save_empty_data2d: bool,
520 metadata_builder: BraidMetadataBuilder,
521 ignore_latency: bool,
522) -> Result<()> {
523 use crate::SaveToDiskMsg::*;
524 use std::time::Duration;
525
526 let mut writing_state: Option<WritingState> = None;
527
528 const FLUSH_INTERVAL: u64 = 1;
529 let flush_interval = Duration::from_secs(FLUSH_INTERVAL);
530
531 tracing::debug!("Starting braidz writer task.");
532
533 while let Some(msg) = braidz_write_rx.blocking_recv() {
534 match msg {
539 KalmanEstimate(ke) => {
540 let KalmanEstimateRecord {
541 record,
542 data_assoc_rows,
543 mean_reproj_dist_100x,
544 production_timestamp,
545 } = ke;
546 let trigger_timestamp = record.timestamp.clone();
547
548 if let Some(ref mut ws) = writing_state {
550 if let Some(ref mut kew) = ws.kalman_estimates_wtr {
551 kew.serialize(record)?;
552 if let Some(count) = ws.writer_stats.as_mut() {
553 count.1 += 1
554 }
555 }
556 if let Some(ref mut daw) = ws.data_assoc_wtr {
557 for row in data_assoc_rows.iter() {
558 daw.serialize(row)?;
559 }
560 }
561
562 if !ignore_latency {
563 if let (Some(trigger_timestamp), Some(produced_at)) =
569 (trigger_timestamp, production_timestamp)
570 {
571 use chrono::{DateTime, Utc};
575 let then: DateTime<Utc> = trigger_timestamp.into();
576 let elapsed = produced_at.signed_duration_since(then);
577 let now_system: std::time::SystemTime = produced_at.into();
578
579 if let Some(latency_usec) = elapsed.num_microseconds()
580 && latency_usec >= 0
581 && let Some(reconstruction_latency_usec) =
582 &mut ws.reconstruction_latency_usec
583 {
584 match histogram_record(
588 latency_usec as u64,
589 &mut reconstruction_latency_usec.current_store,
590 1000 * 1000 * 60,
591 2,
592 ws.file_start_time,
593 &mut reconstruction_latency_usec.histograms,
594 now_system,
595 ) {
596 Ok(()) => {}
597 Err(_) => tracing::error!(
598 "latency value {} out of expected range",
599 latency_usec
600 ),
601 }
602 }
603 }
604 }
605
606 {
607 if let Some(mean_reproj_dist_100x) = mean_reproj_dist_100x {
608 let now_system = std::time::SystemTime::now();
609
610 if let Some(reproj_dist_pixels) = &mut ws.reproj_dist_pixels {
611 match histogram_record(
612 mean_reproj_dist_100x,
613 &mut reproj_dist_pixels.current_store,
614 1000000,
615 2,
616 ws.file_start_time,
617 &mut reproj_dist_pixels.histograms,
618 now_system,
619 ) {
620 Ok(()) => {}
621 Err(_) => tracing::error!(
622 "mean reprojection 100x distance value {} out of expected range",
623 mean_reproj_dist_100x
624 ),
625 }
626 }
627 }
628 }
629 }
630
631 }
633 Data2dDistorted(fdp) => {
634 if let Some(ref mut ws) = writing_state {
635 let rows = ws.save_data_2d_distorted(fdp)?;
636 if let Some(count) = ws.writer_stats.as_mut() {
637 count.0 += rows;
638 }
639 }
640 }
642 StartSavingCsv(cfg) => {
643 writing_state = Some(WritingState::new(
644 cfg,
645 cam_manager.sample(),
646 &recon,
647 tracking_params.clone(),
648 save_empty_data2d,
649 metadata_builder.clone(),
650 )?);
651 }
652 StopSavingCsv => {
653 writing_state = None;
656 }
657 SetExperimentUuid(uuid) => {
658 let entry = ExperimentInfoRow { uuid };
659 if let Some(ref mut ws) = writing_state {
660 ws.experiment_info_wtr.serialize(&entry)?;
661 }
662 }
663 Textlog(entry) => {
664 if let Some(ref mut ws) = writing_state {
665 ws.textlog_wtr.serialize(&entry)?;
666 }
667 }
669 TriggerClockInfo(entry) => {
670 if let Some(ref mut ws) = writing_state {
671 ws.trigger_clock_info_wtr.serialize(&entry)?;
672 }
673 }
675 }
676
677 if let Some(ref mut ws) = writing_state
678 && ws.last_flush.elapsed() > flush_interval
679 {
680 ws.flush_all()?;
681 }
682 }
683 tracing::info!("Done with braidz writer task.");
684 Ok(())
685}
686
687#[cfg(test)]
688mod test {
689 use super::*;
690 use std::sync::atomic::AtomicBool;
691
692 #[test]
693 fn test_save_braidz_on_drop() {
694 let root = tempfile::tempdir().unwrap().keep(); let braid_root = root.join("test.braid");
698 let braidz_name = root.join("test.braidz");
699
700 {
701 let cfg = StartSavingCsvConfig {
702 out_dir: braid_root.clone(),
703 local: None,
704 git_rev: "<impossible git rev>".into(),
705 fps: None,
706 per_cam_data: Default::default(),
707 print_stats: false,
708 save_performance_histograms: false,
709 };
710
711 let cam_manager = ConnectedCamerasManager::new(
712 &None,
713 std::collections::BTreeSet::new(),
714 Arc::new(AtomicBool::new(true)),
715 Arc::new(AtomicBool::new(true)),
716 None,
717 None,
718 );
719 let tracking_params = Arc::new(braid_types::default_tracking_params_full_3d());
720 let save_empty_data2d = false;
721
722 let ws = WritingState::new(
723 cfg,
724 cam_manager.sample(),
725 &None,
726 tracking_params,
727 save_empty_data2d,
728 BraidMetadataBuilder::saving_program_name(format!("{}:{}", file!(), line!())),
729 )
730 .unwrap();
731
732 assert!(braid_root.exists());
734 assert!(!braidz_name.exists());
736
737 std::mem::drop(ws);
738 }
739
740 assert!(!braid_root.exists());
742
743 assert!(braidz_name.exists());
745
746 std::fs::remove_dir_all(root).unwrap();
747 }
748
749 #[ignore]
751 #[test]
752 fn test_giant_braidz_for_zip64_support() -> Result<()> {
753 let root = tempfile::tempdir()?;
754
755 println!("saving giant files in temp dir {}", root.path().display());
756
757 let braid_root = root.path().join("test.braid");
758 let braidz_name = root.path().join("test.braidz");
759
760 fn make_frame_data(i: u64) -> FrameDataAndPoints {
761 let synced_frame = braid_types::SyncFno(i);
762 FrameDataAndPoints {
763 frame_data: crate::FrameData {
764 block_id: None,
765 cam_name: braid_types::RawCamName::new("cam".to_string()),
766 cam_num: braid_types::CamNum(0),
767 cam_received_timestamp: braid_types::FlydraFloatTimestampLocal::from_f64(
768 i as f64 + 0.123,
769 ),
770 device_timestamp: None,
771 synced_frame,
772 tdpt: crate::TimeDataPassthrough {
773 frame: synced_frame,
774 timestamp: None,
775 },
776 time_delta: crate::SyncedFrameCount {
777 frame: synced_frame,
778 },
779 trigger_timestamp: None,
780 },
781 points: vec![],
782 }
783 }
784
785 let num_rows = 1_200_000_000;
787
788 let save_empty_data2d = true;
789 {
790 let cfg = StartSavingCsvConfig {
791 out_dir: braid_root.clone(),
792 local: None,
793 git_rev: "<impossible git rev>".into(),
794 fps: None,
795 per_cam_data: Default::default(),
796 print_stats: false,
797 save_performance_histograms: false,
798 };
799
800 let cam_manager = ConnectedCamerasManager::new(
801 &None,
802 std::collections::BTreeSet::new(),
803 Arc::new(AtomicBool::new(true)),
804 Arc::new(AtomicBool::new(true)),
805 None,
806 None,
807 );
808 let tracking_params = Arc::new(braid_types::default_tracking_params_full_3d());
809
810 let mut ws = WritingState::new(
811 cfg,
812 cam_manager.sample(),
813 &None,
814 tracking_params,
815 save_empty_data2d,
816 BraidMetadataBuilder::saving_program_name(format!("{}:{}", file!(), line!())),
817 )?;
818
819 assert!(braid_root.exists());
821 assert!(!braidz_name.exists());
823
824 for i in 0..num_rows {
826 if i % 10_000_000 == 0 {
827 println!(
828 "writing {}/{}: {}%",
829 i,
830 num_rows,
831 i as f64 / num_rows as f64 * 100.0
832 );
833 }
834 ws.save_data_2d_distorted(make_frame_data(i))?;
835 }
836
837 std::mem::drop(ws);
838 }
839
840 assert!(!braid_root.exists());
842
843 assert!(braidz_name.exists());
845
846 let metadata = std::fs::metadata(&braidz_name)?;
847 println!("metadata.len() {}", metadata.len());
848 assert!(metadata.len() > 5_000_000_000);
849
850 let zip_reader = std::fs::File::open(braidz_name)?;
851 let mut zip_archive = zip::ZipArchive::new(zip_reader).unwrap();
852
853 let data2d_fname = format!("{}.gz", braid_types::DATA2D_DISTORTED_CSV_FNAME);
854
855 let gz_rdr = zip_archive.by_name(&data2d_fname).unwrap();
856
857 let raw_csv_rdr = libflate::gzip::Decoder::new(gz_rdr)?;
858 let csv_rdr = csv::Reader::from_reader(raw_csv_rdr);
859 let csv_rdr2 = csv_rdr.into_deserialize();
860
861 let mut count = 0;
862 for (i, row) in csv_rdr2.into_iter().enumerate() {
863 if i % 10_000_000 == 0 {
864 println!(
865 "reading {}/{}: {}%",
866 i,
867 num_rows,
868 i as f64 / num_rows as f64 * 100.0
869 );
870 }
871 let actual: braid_types::Data2dDistortedRow = row?;
872 let mut expected_rows = make_frame_data(i as u64).into_save(save_empty_data2d);
873 assert_eq!(expected_rows.len(), 1);
874 let expected = expected_rows.pop().unwrap();
875 let actual: braid_types::Data2dDistortedRowF32 = actual.into();
876 assert_eq!(actual.frame, expected.frame);
877 count += 1;
878 }
879
880 assert_eq!(count, num_rows);
881
882 Ok(())
883 }
884}