1extern crate machine_vision_formats as formats;
5
6use anyhow::Context;
7use std::sync::{Arc, Mutex};
8
9use ci2::{
10 AcquisitionMode, AutoMode, DynamicFrameWithInfo, HostTimingInfo, TriggerMode, TriggerSelector,
11};
12use pylon_shimload::HasProperties;
13use strand_dynamic_frame::DynamicFrameOwned;
14
15trait ExtendedError<T> {
16 fn map_pylon_err(self) -> ci2::Result<T>;
17}
18
19impl<T> ExtendedError<T> for std::result::Result<T, pylon_shimload::PylonError> {
20 fn map_pylon_err(self) -> ci2::Result<T> {
21 self.map_err(|pylon_error| ci2::Error::BackendError(anyhow::Error::new(pylon_error)))
22 }
23}
24
25pub type Result<M> = std::result::Result<M, Error>;
26
27const BAD_FNO: usize = usize::MAX;
28
29mod feature_cache;
30use feature_cache::*;
31
32#[derive(thiserror::Error, Debug)]
33pub enum Error {
34 #[error("Pylon error: {source}")]
35 PylonError {
36 #[from]
37 source: pylon_shimload::PylonError,
38 },
39 #[error("int parse error: {source}")]
40 IntParseError {
41 #[from]
42 source: std::num::ParseIntError,
43 },
44 #[error("other error: {msg}")]
45 OtherError { msg: String },
46}
47
48impl From<Error> for ci2::Error {
49 fn from(orig: Error) -> ci2::Error {
50 ci2::Error::BackendError(orig.into())
51 }
52}
53
54pub struct WrappedModule {}
55
56fn to_name(info: &pylon_shimload::DeviceInfo) -> String {
57 let serial = &info.property_value("SerialNumber").unwrap();
60 let vendor = &info.property_value("VendorName").unwrap();
61 format!("{}-{}", vendor, serial)
62}
63
64pub fn new_module() -> ci2::Result<WrappedModule> {
65 Ok(WrappedModule {})
66}
67
68pub struct PylonTerminateGuard {}
71
72pub fn make_singleton_guard(
73 _pylon_module: &dyn ci2::CameraModule<CameraType = WrappedCamera, Guard = PylonTerminateGuard>,
74) -> ci2::Result<PylonTerminateGuard> {
75 Ok(PylonTerminateGuard {})
76}
77
78impl<'a> ci2::CameraModule for &'a WrappedModule {
79 type CameraType = WrappedCamera;
80 type Guard = PylonTerminateGuard;
81
82 fn name(self: &&'a WrappedModule) -> &'static str {
83 "pylon"
84 }
85 fn camera_infos(self: &&'a WrappedModule) -> ci2::Result<Vec<Box<dyn ci2::CameraInfo>>> {
86 let pylon_infos = pylon_shimload::enumerate_devices()
87 .map_pylon_err()
88 .context("enumerate_devices")?;
89 let infos = pylon_infos
90 .into_iter()
91 .map(|info| {
92 let serial = info.property_value("SerialNumber").unwrap();
93 let model = info.property_value("ModelName").unwrap();
94 let vendor = info.property_value("VendorName").unwrap();
95 let name = to_name(&info);
96 let pci = Box::new(PylonCameraInfo {
97 name,
98 serial,
99 model,
100 vendor,
101 });
102 let ci: Box<dyn ci2::CameraInfo> = pci; ci
104 })
105 .collect();
106 Ok(infos)
107 }
108 fn camera(self: &mut &'a WrappedModule, name: &str) -> ci2::Result<Self::CameraType> {
109 WrappedCamera::new(name)
110 }
111 fn settings_file_extension(&self) -> &str {
112 "pfs" }
115}
116
117#[derive(Debug)]
118struct PylonCameraInfo {
119 name: String,
120 serial: String,
121 model: String,
122 vendor: String,
123}
124
125impl ci2::CameraInfo for PylonCameraInfo {
126 fn name(&self) -> &str {
127 &self.name
128 }
129 fn serial(&self) -> &str {
130 &self.serial
131 }
132 fn model(&self) -> &str {
133 &self.model
134 }
135 fn vendor(&self) -> &str {
136 &self.vendor
137 }
138}
139
140#[derive(Clone)]
141pub struct WrappedCamera {
142 inner: Arc<Mutex<pylon_shimload::InstantCamera>>,
143 store_fno: usize,
144 name: String,
145 serial: String,
146 model: String,
147 vendor: String,
148 grab_result: Arc<Mutex<pylon_shimload::GrabResult>>,
149 is_sfnc2: bool,
150 pfs_cache: Arc<Mutex<PfsCache>>,
151}
152
153fn _test_camera_is_send() {
154 fn implements<T: Send>() {}
156 implements::<WrappedCamera>();
157}
158
159impl WrappedCamera {
160 fn new(name: &str) -> ci2::Result<Self> {
161 let max_u64_as_usize: usize = u64::MAX.try_into().unwrap();
162 assert_eq!(max_u64_as_usize, BAD_FNO);
163
164 let devices = pylon_shimload::enumerate_devices().context("enumerate_devices")?;
165
166 for device_info in devices.into_iter() {
167 let this_name = to_name(&device_info);
168 if this_name == name {
169 let serial = device_info
170 .property_value("SerialNumber")
171 .context("getting serial")?;
172 let model = device_info
173 .property_value("ModelName")
174 .context("getting model")?;
175 let vendor = device_info
176 .property_value("VendorName")
177 .context("getting vendor")?;
178 let store_fno = 0;
179
180 let cam = pylon_shimload::create_device(&device_info).context("creating device")?;
181 cam.open().context("opening camera")?;
182
183 let is_sfnc2 = match cam
184 .node_map()
185 .map_pylon_err()?
186 .integer_node("DeviceSFNCVersionMajor")
187 .map_pylon_err()?
188 .value()
189 {
190 Ok(major) => major >= 2,
191 Err(_) => false,
192 };
193
194 let set_max_transfer_size = match std::env::var_os("DISABLE_SET_MAX_TRANSFER_SIZE")
195 {
196 Some(v) => &v == "0",
197 None => true,
198 };
199
200 if set_max_transfer_size {
201 let mut node = cam
207 .stream_grabber_node_map()
208 .map_pylon_err()?
209 .integer_node("MaxTransferSize")
210 .map_pylon_err()?;
211
212 if let Ok(max_size) = node.max() {
213 node.set_value(max_size).map_pylon_err()?;
217 tracing::debug!(
218 "For camera {}, set stream grabber MaxTransferSize to {}",
219 name,
220 max_size
221 );
222
223 #[cfg(target_os = "linux")]
224 {
225 let fname = "/sys/module/usbcore/parameters/usbfs_memory_mb";
227 match std::fs::read_to_string(fname) {
228 Ok(usbfs_memory_mb) => {
229 let usbfs_memory_mb: i64 =
230 usbfs_memory_mb.trim().parse().unwrap();
231 let desired_mb = 1000;
232 if usbfs_memory_mb < desired_mb {
233 tracing::warn!(
234 "You seem to be using a USB3 camera on linux but the file \"{}\" \
235 is set to only {}. For best performance, consider setting it to {}. \
236 For more information, see \
237 https://web.archive.org/web/20230318224225/https://www.baslerweb.com/en/sales-support/knowledge-base/frequently-asked-questions/how-can-i-set-the-usbfs-on-linux-or-linux-for-arm-to-prevent-image-losses-with-pylon-and-usb-cameras/29826/.",
238 fname,
239 usbfs_memory_mb,
240 desired_mb
241 );
242 } else {
243 tracing::debug!(
244 "File \"{}\" indicates a value of {}.",
245 fname,
246 usbfs_memory_mb
247 );
248 }
249 }
250 Err(e) => {
251 tracing::warn!(
252 "Could not read {} to check USB subsystem memory due to error: {}",
253 fname,
254 e
255 );
256 }
257 }
258
259 match rlimit::Resource::NOFILE.get() {
262 Ok((soft, _hard)) => {
263 let desired = 4096;
264 if soft < desired {
265 tracing::warn!(
266 "You seem to be using linux but you have only {} file descriptors available. \
267 For best performance, set this to at least {}. See https://github.com/basler/pypylon/issues/80#issuecomment-461727225 \
268 for more information. Hint: use 'ulimit -n 4096' to update.",
269 soft,
270 desired
271 );
272 }
273 }
274 Err(e) => {
275 tracing::warn!(
276 "Could not check max number of open file descriptors due to error: {}",
277 e
278 );
279 }
280 }
281 }
282 }
283 }
284
285 let pfs_cache = {
286 let node_map = cam.node_map().map_pylon_err()?;
287 let settings = node_map.save_to_string().map_pylon_err()?;
288 PfsCache::new_from_string(settings)?
289 };
290 let pfs_cache = Arc::new(Mutex::new(pfs_cache));
291
292 let grab_result = Arc::new(Mutex::new(
293 pylon_shimload::GrabResult::new().map_pylon_err()?,
294 ));
295 return Ok(Self {
296 inner: Arc::new(Mutex::new(cam)),
298 name: name.to_string(),
299 store_fno,
300 serial,
301 model,
302 vendor,
303 grab_result,
304 is_sfnc2,
305 pfs_cache,
306 });
307 }
308 }
309 Err(Error::OtherError {
310 msg: format!("requested camera '{}' was not found", name),
311 }
312 .into())
313 }
314
315 fn exposure_time_param_name(&self) -> &'static str {
316 if self.is_sfnc2 {
317 "ExposureTime"
318 } else {
319 "ExposureTimeRaw"
320 }
321 }
322
323 fn acquisition_frame_rate_name(&self) -> &'static str {
324 if self.is_sfnc2 {
325 "AcquisitionFrameRate"
326 } else {
327 "AcquisitionFrameRateAbs"
328 }
329 }
330}
331
332impl ci2::CameraInfo for WrappedCamera {
333 fn name(&self) -> &str {
334 &self.name
335 }
336 fn serial(&self) -> &str {
337 &self.serial
338 }
339 fn model(&self) -> &str {
340 &self.model
341 }
342 fn vendor(&self) -> &str {
343 &self.vendor
344 }
345}
346
347impl ci2::Camera for WrappedCamera {
348 fn command_execute(&self, name: &str, verify: bool) -> ci2::Result<()> {
355 let camera = self.inner.lock().unwrap();
356 camera
357 .node_map()
358 .map_pylon_err()?
359 .command_node(name)
360 .map_pylon_err()?
361 .execute(verify)
362 .map_pylon_err()
363 }
364
365 fn feature_bool(&self, name: &str) -> ci2::Result<bool> {
366 let camera = self.inner.lock().unwrap();
367 camera
368 .node_map()
369 .map_pylon_err()?
370 .boolean_node(name)
371 .map_pylon_err()?
372 .value()
373 .map_pylon_err()
374 }
375
376 fn feature_bool_set(&self, name: &str, value: bool) -> ci2::Result<()> {
377 let camera = self.inner.lock().unwrap();
378 camera
379 .node_map()
380 .map_pylon_err()?
381 .boolean_node(name)
382 .map_pylon_err()?
383 .set_value(value)
384 .map_pylon_err()
385 }
386
387 fn feature_enum(&self, name: &str) -> ci2::Result<String> {
388 let camera = self.inner.lock().unwrap();
389 let node = camera
390 .node_map()
391 .map_pylon_err()?
392 .enum_node(name)
393 .map_pylon_err()?;
394 node.value().map_pylon_err()
395 }
396
397 fn feature_enum_set(&self, name: &str, value: &str) -> ci2::Result<()> {
398 let camera = self.inner.lock().unwrap();
399 let mut node = camera
400 .node_map()
401 .map_pylon_err()?
402 .enum_node(name)
403 .map_pylon_err()?;
404 node.set_value_pfs(&mut self.pfs_cache.lock().unwrap(), value)
405 .map_pylon_err()
406 }
407
408 fn feature_float(&self, name: &str) -> ci2::Result<f64> {
409 let camera = self.inner.lock().unwrap();
410 camera
411 .node_map()
412 .map_pylon_err()?
413 .float_node(name)
414 .map_pylon_err()?
415 .value()
416 .map_pylon_err()
417 }
418
419 fn feature_float_set(&self, name: &str, value: f64) -> ci2::Result<()> {
420 let camera = self.inner.lock().unwrap();
421 camera
422 .node_map()
423 .map_pylon_err()?
424 .float_node(name)
425 .map_pylon_err()?
426 .set_value(value)
427 .map_pylon_err()
428 }
429
430 fn feature_int(&self, name: &str) -> ci2::Result<i64> {
431 let camera = self.inner.lock().unwrap();
432 camera
433 .node_map()
434 .map_pylon_err()?
435 .integer_node(name)
436 .map_pylon_err()?
437 .value()
438 .map_pylon_err()
439 }
440
441 fn feature_int_set(&self, name: &str, value: i64) -> ci2::Result<()> {
442 let camera = self.inner.lock().unwrap();
443 camera
444 .node_map()
445 .map_pylon_err()?
446 .integer_node(name)
447 .map_pylon_err()?
448 .set_value(value)
449 .map_pylon_err()
450 }
451
452 fn node_map_load(&self, settings: &str) -> ci2::Result<()> {
455 let settings_lf_only = settings.lines().collect::<Vec<_>>().join("\n");
459
460 let camera = self.inner.lock().unwrap();
461 camera
462 .node_map()
463 .map_pylon_err()?
464 .load_from_string(settings_lf_only, true)
465 .map_pylon_err()?;
466 Ok(())
467 }
468
469 fn node_map_save(&self) -> ci2::Result<String> {
470 Ok(self.pfs_cache.lock().unwrap().to_header_string())
474 }
475
476 fn width(&self) -> ci2::Result<u32> {
478 Ok(self
479 .inner
480 .lock()
481 .unwrap()
482 .node_map()
483 .map_pylon_err()?
484 .integer_node("Width")
485 .map_pylon_err()?
486 .value()
487 .map_pylon_err()?
488 .try_into()?)
489 }
490 fn height(&self) -> ci2::Result<u32> {
492 Ok(self
493 .inner
494 .lock()
495 .unwrap()
496 .node_map()
497 .map_pylon_err()?
498 .integer_node("Height")
499 .map_pylon_err()?
500 .value()
501 .map_pylon_err()?
502 .try_into()?)
503 }
504
505 fn pixel_format(&self) -> ci2::Result<formats::PixFmt> {
507 let camera = self.inner.lock().unwrap();
508 let pixel_format_node = camera
509 .node_map()
510 .map_pylon_err()?
511 .enum_node("PixelFormat")
512 .map_pylon_err()?;
513 convert_to_pixel_format(pixel_format_node.value().map_pylon_err()?.as_ref())
514 }
515 fn possible_pixel_formats(&self) -> ci2::Result<Vec<formats::PixFmt>> {
516 let camera = self.inner.lock().unwrap();
517 let pixel_format_node = camera
518 .node_map()
519 .map_pylon_err()?
520 .enum_node("PixelFormat")
521 .map_pylon_err()?;
522 Ok(pixel_format_node
524 .settable_values()
525 .map_pylon_err()?
526 .iter()
527 .filter_map(|string_val| convert_to_pixel_format(string_val).ok())
528 .collect::<Vec<formats::PixFmt>>())
529 }
537 fn set_pixel_format(&mut self, pixel_format: formats::PixFmt) -> ci2::Result<()> {
538 let camera = self.inner.lock().unwrap();
539 let mut pixel_format_node = camera
540 .node_map()
541 .map_pylon_err()?
542 .enum_node("PixelFormat")
543 .map_pylon_err()?;
544 let available = pixel_format_node.settable_values().map_pylon_err()?;
547 let s = choose_pixel_format_name(pixel_format, &available)?;
548 pixel_format_node
549 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), s)
550 .map_pylon_err()
551 }
552
553 fn exposure_time(&self) -> ci2::Result<f64> {
556 let camera = self.inner.lock().unwrap();
557 let name = self.exposure_time_param_name();
558 if self.is_sfnc2 {
559 camera
560 .node_map()
561 .map_pylon_err()?
562 .float_node(name)
563 .map_pylon_err()?
564 .value()
565 .map_pylon_err()
566 } else {
567 camera
568 .node_map()
569 .map_pylon_err()?
570 .integer_node(name)
571 .map_pylon_err()?
572 .value()
573 .map_pylon_err()
574 .map(|x| x as f64)
575 }
576 }
577
578 fn exposure_time_range(&self) -> ci2::Result<(f64, f64)> {
580 let camera = self.inner.lock().unwrap();
581 let name = self.exposure_time_param_name();
582 if self.is_sfnc2 {
583 let node = camera
584 .node_map()
585 .map_pylon_err()?
586 .float_node(name)
587 .map_pylon_err()?;
588 Ok((node.min().map_pylon_err()?, node.max().map_pylon_err()?))
589 } else {
590 let node = camera
591 .node_map()
592 .map_pylon_err()?
593 .integer_node(name)
594 .map_pylon_err()?;
595 Ok((
596 node.min().map_pylon_err()? as f64,
597 node.max().map_pylon_err()? as f64,
598 ))
599 }
600 }
601
602 fn set_exposure_time(&mut self, value: f64) -> ci2::Result<()> {
604 let camera = self.inner.lock().unwrap();
605 let name = self.exposure_time_param_name();
606 if self.is_sfnc2 {
607 camera
608 .node_map()
609 .map_pylon_err()?
610 .float_node(name)
611 .map_pylon_err()?
612 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), value)
613 .map_pylon_err()
614 } else {
615 camera
616 .node_map()
617 .map_pylon_err()?
618 .integer_node(name)
619 .map_pylon_err()?
620 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), value.round() as i64)
621 .map_pylon_err()
622 }
623 }
624
625 fn exposure_auto(&self) -> ci2::Result<AutoMode> {
627 let camera = self.inner.lock().unwrap();
628 let val = camera
629 .node_map()
630 .map_pylon_err()?
631 .enum_node("ExposureAuto")
632 .map_pylon_err()?
633 .value()
634 .map_pylon_err()?;
635 str_to_auto_mode(val.as_ref())
636 }
637 fn set_exposure_auto(&mut self, value: AutoMode) -> ci2::Result<()> {
638 let sval = mode_to_str(value);
639 self.inner
640 .lock()
641 .unwrap()
642 .node_map()
643 .map_pylon_err()?
644 .enum_node("ExposureAuto")
645 .map_pylon_err()?
646 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), sval)
647 .map_pylon_err()
648 }
649
650 fn gain(&self) -> ci2::Result<f64> {
653 let camera = self.inner.lock().unwrap();
654 if self.is_sfnc2 {
655 camera
656 .node_map()
657 .map_pylon_err()?
658 .float_node("Gain")
659 .map_pylon_err()?
660 .value()
661 .map_pylon_err()
662 } else {
663 let gain_raw = camera
664 .node_map()
665 .map_pylon_err()?
666 .integer_node("GainRaw")
667 .map_pylon_err()?
668 .value()
669 .map_pylon_err()?;
670
671 let gain_db = gain_raw_to_db(gain_raw)?;
672 Ok(gain_db as f64)
674 }
675 }
676 fn gain_range(&self) -> ci2::Result<(f64, f64)> {
678 let camera = self.inner.lock().unwrap();
679 if self.is_sfnc2 {
680 let gain_node = camera
681 .node_map()
682 .map_pylon_err()?
683 .float_node("Gain")
684 .map_pylon_err()?;
685 Ok((
686 gain_node.min().map_pylon_err()?,
687 gain_node.max().map_pylon_err()?,
688 ))
689 } else {
690 let gain_node = camera
691 .node_map()
692 .map_pylon_err()?
693 .integer_node("GainRaw")
694 .map_pylon_err()?;
695
696 let gain_min = gain_node.min().map_pylon_err()?;
697 let gain_max = gain_node.max().map_pylon_err()?;
698
699 let gain_min_db = gain_raw_to_db(gain_min)?;
700 let gain_max_db = gain_raw_to_db(gain_max)?;
701 Ok((gain_min_db, gain_max_db))
702 }
703 }
704
705 fn set_gain(&mut self, gain_db: f64) -> ci2::Result<()> {
707 let camera = self.inner.lock().unwrap();
708 if self.is_sfnc2 {
709 camera
710 .node_map()
711 .map_pylon_err()?
712 .float_node("Gain")
713 .map_pylon_err()?
714 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), gain_db)
715 .map_pylon_err()?;
716 } else {
717 let gain_raw = gain_db_to_raw(gain_db)?;
718 camera
719 .node_map()
720 .map_pylon_err()?
721 .integer_node("GainRaw")
722 .map_pylon_err()?
723 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), gain_raw)
724 .map_pylon_err()?;
725 }
726 Ok(())
727 }
728
729 fn gain_auto(&self) -> ci2::Result<AutoMode> {
731 let camera = self.inner.lock().unwrap();
732 let val = camera
733 .node_map()
734 .map_pylon_err()?
735 .enum_node("GainAuto")
736 .map_pylon_err()?
737 .value()
738 .map_pylon_err()?;
739 str_to_auto_mode(val.as_ref())
740 }
741
742 fn set_gain_auto(&mut self, value: AutoMode) -> ci2::Result<()> {
743 let sval = mode_to_str(value);
744 self.inner
745 .lock()
746 .unwrap()
747 .node_map()
748 .map_pylon_err()?
749 .enum_node("GainAuto")
750 .map_pylon_err()?
751 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), sval)
752 .map_pylon_err()
753 }
754
755 fn trigger_mode(&self) -> ci2::Result<TriggerMode> {
757 let camera = self.inner.lock().unwrap();
758 let val = camera
759 .node_map()
760 .map_pylon_err()?
761 .enum_node("TriggerMode")
762 .map_pylon_err()?
763 .value()
764 .map_pylon_err()?;
765 match val.as_ref() {
766 "Off" => Ok(ci2::TriggerMode::Off),
767 "On" => Ok(ci2::TriggerMode::On),
768 s => Err(ci2::Error::from(format!(
769 "unexpected TriggerMode enum string: {}",
770 s
771 ))),
772 }
773 }
774 fn set_trigger_mode(&mut self, value: TriggerMode) -> ci2::Result<()> {
775 let sval = match value {
776 ci2::TriggerMode::Off => "Off",
777 ci2::TriggerMode::On => "On",
778 };
779 self.inner
780 .lock()
781 .unwrap()
782 .node_map()
783 .map_pylon_err()?
784 .enum_node("TriggerMode")
785 .map_pylon_err()?
786 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), sval)
787 .map_pylon_err()
788 }
789
790 fn acquisition_frame_rate_enable(&self) -> ci2::Result<bool> {
792 self.inner
793 .lock()
794 .unwrap()
795 .node_map()
796 .map_pylon_err()?
797 .boolean_node("AcquisitionFrameRateEnable")
798 .map_pylon_err()?
799 .value()
800 .map_pylon_err()
801 }
802 fn set_acquisition_frame_rate_enable(&mut self, value: bool) -> ci2::Result<()> {
803 self.inner
804 .lock()
805 .unwrap()
806 .node_map()
807 .map_pylon_err()?
808 .boolean_node("AcquisitionFrameRateEnable")
809 .map_pylon_err()?
810 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), value)
811 .map_pylon_err()
812 }
813
814 fn acquisition_frame_rate(&self) -> ci2::Result<f64> {
816 let camera = self.inner.lock().unwrap();
817 let node = camera
818 .node_map()
819 .map_pylon_err()?
820 .float_node(self.acquisition_frame_rate_name())
821 .map_pylon_err()?;
822 node.value().map_pylon_err()
823 }
824 fn acquisition_frame_rate_range(&self) -> ci2::Result<(f64, f64)> {
825 let camera = self.inner.lock().unwrap();
826 let node = camera
827 .node_map()
828 .map_pylon_err()?
829 .float_node(self.acquisition_frame_rate_name())
830 .map_pylon_err()?;
831 Ok((node.min().map_pylon_err()?, node.max().map_pylon_err()?))
832 }
833 fn set_acquisition_frame_rate(&mut self, value: f64) -> ci2::Result<()> {
834 self.inner
835 .lock()
836 .unwrap()
837 .node_map()
838 .map_pylon_err()?
839 .float_node(self.acquisition_frame_rate_name())
840 .map_pylon_err()?
841 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), value)
842 .map_pylon_err()
843 }
844
845 fn trigger_selector(&self) -> ci2::Result<TriggerSelector> {
847 let camera = self.inner.lock().unwrap();
848 let val = camera
849 .node_map()
850 .map_pylon_err()?
851 .enum_node("TriggerSelector")
852 .map_pylon_err()?
853 .value()
854 .map_pylon_err()?;
855 match val.as_ref() {
856 "AcquisitionStart" => Ok(ci2::TriggerSelector::AcquisitionStart),
857 "FrameBurstStart" => Ok(ci2::TriggerSelector::FrameBurstStart),
858 "FrameStart" => Ok(ci2::TriggerSelector::FrameStart),
859 "ExposureActive" => Ok(ci2::TriggerSelector::ExposureActive),
860 s => Err(ci2::Error::from(format!(
861 "unexpected TriggerSelector enum string: {}",
862 s
863 ))),
864 }
865 }
866 fn set_trigger_selector(&mut self, value: TriggerSelector) -> ci2::Result<()> {
867 let sval = match value {
868 ci2::TriggerSelector::AcquisitionStart => "AcquisitionStart",
869 ci2::TriggerSelector::FrameBurstStart => "FrameBurstStart",
870 ci2::TriggerSelector::FrameStart => "FrameStart",
871 ci2::TriggerSelector::ExposureActive => "ExposureActive",
872 s => {
873 return Err(ci2::Error::from(format!(
874 "unexpected TriggerSelector: {:?}",
875 s
876 )));
877 }
878 };
879 let camera = self.inner.lock().unwrap();
880 camera
881 .node_map()
882 .map_pylon_err()?
883 .enum_node("TriggerSelector")
884 .map_pylon_err()?
885 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), sval)
886 .map_pylon_err()
887 }
888
889 fn acquisition_mode(&self) -> ci2::Result<AcquisitionMode> {
891 let mode = self
892 .inner
893 .lock()
894 .unwrap()
895 .node_map()
896 .map_pylon_err()?
897 .enum_node("AcquisitionMode")
898 .map_pylon_err()?
899 .value()
900 .map_pylon_err()?;
901 Ok(match mode.as_ref() {
902 "Continuous" => ci2::AcquisitionMode::Continuous,
903 "SingleFrame" => ci2::AcquisitionMode::SingleFrame,
904 "MultiFrame" => ci2::AcquisitionMode::MultiFrame,
905 s => {
906 return Err(ci2::Error::from(format!(
907 "unexpected AcquisitionMode: {:?}",
908 s
909 )));
910 }
911 })
912 }
913 fn set_acquisition_mode(&mut self, value: ci2::AcquisitionMode) -> ci2::Result<()> {
914 let sval = match value {
915 ci2::AcquisitionMode::Continuous => "Continuous",
916 ci2::AcquisitionMode::SingleFrame => "SingleFrame",
917 ci2::AcquisitionMode::MultiFrame => "MultiFrame",
918 };
919 self.inner
920 .lock()
921 .unwrap()
922 .node_map()
923 .map_pylon_err()?
924 .enum_node("AcquisitionMode")
925 .map_pylon_err()?
926 .set_value_pfs(&mut self.pfs_cache.lock().unwrap(), sval)
927 .map_pylon_err()
928 }
929
930 fn acquisition_start(&mut self) -> ci2::Result<()> {
932 self.inner
933 .lock()
934 .unwrap()
935 .start_grabbing(&pylon_shimload::GrabOptions::default())
936 .map_pylon_err()?;
937 Ok(())
938 }
939 fn acquisition_stop(&mut self) -> ci2::Result<()> {
940 self.inner.lock().unwrap().stop_grabbing().map_pylon_err()?;
941 Ok(())
942 }
943
944 fn next_frame(&mut self) -> ci2::Result<DynamicFrameWithInfo> {
946 let pixel_format = self.pixel_format()?;
947
948 let mut gr = self.grab_result.lock().unwrap();
949 let cam = self.inner.lock().unwrap();
950
951 cam.retrieve_result(
953 99999,
954 &mut gr,
955 pylon_shimload::TimeoutHandling::ThrowException,
956 )
957 .map_pylon_err()?;
958
959 let now = chrono::Utc::now(); if gr.grab_succeeded().map_pylon_err()? {
963 let buffer = gr.buffer().map_pylon_err()?;
964 let block_id = gr.block_id().map_pylon_err()?;
965
966 let fno: usize = self.store_fno;
967 self.store_fno += 1;
968
969 let width = gr.width().map_pylon_err()?;
970 let height = gr.height().map_pylon_err()?;
971 let stride = gr.stride().map_pylon_err()?;
972 let image_data = buffer.to_vec();
973 let device_timestamp = gr.time_stamp().map_pylon_err()?;
974
975 let backend_data = if !(device_timestamp == 0 && block_id == u64::MAX) {
976 Some(Box::new(ci2_pylon_types::PylonExtra {
977 block_id,
978 device_timestamp,
979 }) as Box<dyn ci2::BackendData>)
980 } else {
981 None
984 };
985
986 let host_timing = HostTimingInfo { fno, datetime: now };
987 let image = Arc::new(
988 DynamicFrameOwned::from_buf(width, height, stride, image_data, pixel_format)
989 .unwrap(),
990 );
991
992 Ok(DynamicFrameWithInfo {
993 image,
994 host_timing,
995 backend_data,
996 })
997
998 } else {
1000 self.store_fno += 1;
1001
1002 Err(ci2::Error::SingleFrameError(format!(
1003 "Pylon Error {}: {}",
1004 gr.error_code().map_pylon_err()?,
1005 gr.error_description().map_pylon_err()?
1006 )))
1007 }
1008 }
1009}
1010
1011pub fn convert_pixel_format(pixel_format: formats::PixFmt) -> ci2::Result<&'static str> {
1012 Ok(pixel_format_name_candidates(pixel_format)?[0])
1016}
1017
1018fn pixel_format_name_candidates(pixel_format: formats::PixFmt) -> ci2::Result<Vec<&'static str>> {
1028 use formats::PixFmt::*;
1029 let pixfmt = match pixel_format {
1030 Mono8 => vec!["Mono8"],
1031
1032 YUV422 => vec!["YCbCr422_8", "YUV422Packed", "YUV422packed"],
1041 RGB8 => vec!["RGB8", "RGB8Packed", "RGB8packed"],
1042
1043 BayerGR8 => vec!["BayerGR8"],
1044 BayerRG8 => vec!["BayerRG8"],
1045 BayerBG8 => vec!["BayerBG8"],
1046 BayerGB8 => vec!["BayerGB8"],
1047 unknown => {
1051 return Err(ci2::Error::from(format!("Unsuppored PixFmt {}", unknown)));
1052 }
1053 };
1054 Ok(pixfmt)
1055}
1056
1057fn choose_pixel_format_name(
1064 pixel_format: formats::PixFmt,
1065 available: &[String],
1066) -> ci2::Result<&'static str> {
1067 let candidates = pixel_format_name_candidates(pixel_format)?;
1068 candidates
1069 .iter()
1070 .copied()
1071 .find(|candidate| available.iter().any(|a| a == candidate))
1072 .ok_or_else(|| {
1073 ci2::Error::from(format!(
1074 "camera does not support pixel format {pixel_format} \
1075 (tried {candidates:?}, camera offers {available:?})"
1076 ))
1077 })
1078}
1079
1080pub fn convert_to_pixel_format(orig: &str) -> ci2::Result<formats::PixFmt> {
1081 use formats::PixFmt::*;
1082 let pixfmt = match orig {
1083 "Mono8" => Mono8,
1084 "YUV422packed" | "YUV422Packed" | "YCbCr422_8" => YUV422,
1093 "RGB8packed" | "RGB8Packed" | "RGB8" => RGB8,
1094
1095 "BayerGR8" => BayerGR8,
1096 "BayerRG8" => BayerRG8,
1097 "BayerGB8" => BayerGB8,
1098 "BayerBG8" => BayerBG8,
1099
1100 e => {
1101 return Err(ci2::Error::from(format!(
1102 "Unknown pixel format string: {:?}",
1103 e
1104 )));
1105 }
1106 };
1107 Ok(pixfmt)
1108}
1109
1110fn gain_raw_to_db(raw: i64) -> ci2::Result<f64> {
1111 Ok(0.0359 * raw as f64)
1115}
1116
1117fn gain_db_to_raw(db: f64) -> ci2::Result<i64> {
1118 Ok((db / 0.0359) as i64)
1122}
1123
1124fn str_to_auto_mode(val: &str) -> ci2::Result<ci2::AutoMode> {
1125 match val {
1126 "Off" => Ok(ci2::AutoMode::Off),
1127 "Once" => Ok(ci2::AutoMode::Once),
1128 "Continuous" => Ok(ci2::AutoMode::Continuous),
1129 s => Err(ci2::Error::from(format!(
1130 "unexpected AutoMode enum string: {}",
1131 s
1132 ))),
1133 }
1134}
1135
1136fn mode_to_str(value: AutoMode) -> &'static str {
1137 match value {
1138 ci2::AutoMode::Off => "Off",
1139 ci2::AutoMode::Once => "Once",
1140 ci2::AutoMode::Continuous => "Continuous",
1141 }
1142}
1143
1144#[cfg(test)]
1145mod tests {
1146 use super::*;
1147
1148 #[test]
1157 fn test_modern_pixel_format_names_recognized() {
1158 assert_eq!(
1159 convert_to_pixel_format("RGB8").unwrap(),
1160 formats::PixFmt::RGB8
1161 );
1162 assert_eq!(
1163 convert_to_pixel_format("YCbCr422_8").unwrap(),
1164 formats::PixFmt::YUV422
1165 );
1166 }
1167
1168 fn names(vals: &[&str]) -> Vec<String> {
1169 vals.iter().map(|s| s.to_string()).collect()
1170 }
1171
1172 #[test]
1176 fn test_choose_pixel_format_name_ace2() {
1177 let offered = names(&["Mono8", "BayerRG8", "RGB8", "YCbCr422_8"]);
1178 assert_eq!(
1179 choose_pixel_format_name(formats::PixFmt::RGB8, &offered).unwrap(),
1180 "RGB8"
1181 );
1182 assert_eq!(
1183 choose_pixel_format_name(formats::PixFmt::YUV422, &offered).unwrap(),
1184 "YCbCr422_8"
1185 );
1186 }
1187
1188 #[test]
1190 fn test_choose_pixel_format_name_legacy() {
1191 let offered = names(&["Mono8", "BayerRG8", "RGB8packed", "YUV422packed"]);
1192 assert_eq!(
1193 choose_pixel_format_name(formats::PixFmt::RGB8, &offered).unwrap(),
1194 "RGB8packed"
1195 );
1196 assert_eq!(
1197 choose_pixel_format_name(formats::PixFmt::YUV422, &offered).unwrap(),
1198 "YUV422packed"
1199 );
1200 }
1201
1202 #[test]
1207 fn test_choose_pixel_format_name_emulator() {
1208 let offered = names(&[
1209 "Mono8",
1210 "Mono10",
1211 "Mono12",
1212 "Mono16",
1213 "BGRA8Packed",
1214 "BGR8Packed",
1215 "RGB8Packed",
1216 "RGB16Packed",
1217 "BayerGR8",
1218 "BayerRG8",
1219 "BayerGB8",
1220 "BayerBG8",
1221 ]);
1222 assert_eq!(
1223 choose_pixel_format_name(formats::PixFmt::RGB8, &offered).unwrap(),
1224 "RGB8Packed"
1225 );
1226 assert_eq!(
1227 convert_to_pixel_format("RGB8Packed").unwrap(),
1228 formats::PixFmt::RGB8
1229 );
1230 }
1231
1232 #[test]
1235 fn test_choose_pixel_format_name_unsupported() {
1236 let offered = names(&["Mono8", "BayerRG8"]);
1237 assert!(choose_pixel_format_name(formats::PixFmt::RGB8, &offered).is_err());
1238 }
1239
1240 #[test]
1243 fn test_pixel_format_roundtrip() {
1244 for pixfmt in [
1245 formats::PixFmt::Mono8,
1246 formats::PixFmt::RGB8,
1247 formats::PixFmt::YUV422,
1248 formats::PixFmt::BayerGR8,
1249 formats::PixFmt::BayerRG8,
1250 formats::PixFmt::BayerGB8,
1251 formats::PixFmt::BayerBG8,
1252 ] {
1253 for name in pixel_format_name_candidates(pixfmt).unwrap() {
1254 assert_eq!(
1255 convert_to_pixel_format(name).unwrap(),
1256 pixfmt,
1257 "round-trip failed for name {name:?}"
1258 );
1259 }
1260 assert_eq!(
1262 convert_to_pixel_format(convert_pixel_format(pixfmt).unwrap()).unwrap(),
1263 pixfmt
1264 );
1265 }
1266 }
1267}