1use crate::IntensityTable;
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct Spot {
5 center: [f32; 3],
6 radius: f32,
7 quality: f32,
8}
9
10impl Spot {
11 pub fn new(center: [f32; 3], radius: f32, quality: f32) -> Self {
12 assert!(
13 center.iter().all(|value| value.is_finite()),
14 "a spot center must be finite"
15 );
16 assert!(
17 radius.is_finite() && radius > 0.0,
18 "a spot radius must be a positive length"
19 );
20 assert!(
21 quality.is_finite(),
22 "a spot quality must be a finite contrast"
23 );
24 Self {
25 center,
26 radius,
27 quality,
28 }
29 }
30
31 pub fn center(self) -> [f32; 3] {
32 self.center
33 }
34
35 pub fn radius(self) -> f32 {
36 self.radius
37 }
38
39 pub fn quality(self) -> f32 {
40 self.quality
41 }
42}
43
44#[derive(Clone, Debug, PartialEq)]
45pub struct SpotSet {
46 level: usize,
47 spots: Vec<Spot>,
48 intensities: IntensityTable,
49}
50
51impl SpotSet {
52 pub fn new(level: usize, spots: Vec<Spot>, intensities: IntensityTable) -> Self {
53 assert_eq!(
54 intensities.objects(),
55 spots.len(),
56 "every spot is measured in every channel"
57 );
58 Self {
59 level,
60 spots,
61 intensities,
62 }
63 }
64
65 pub fn level(&self) -> usize {
66 self.level
67 }
68
69 pub fn spots(&self) -> &[Spot] {
70 &self.spots
71 }
72
73 pub fn intensities(&self) -> &IntensityTable {
74 &self.intensities
75 }
76
77 pub fn len(&self) -> usize {
78 self.spots.len()
79 }
80
81 pub fn is_empty(&self) -> bool {
82 self.spots.is_empty()
83 }
84
85 pub fn mean_quality(&self) -> Option<f32> {
86 if self.spots.is_empty() {
87 return None;
88 }
89 let total: f32 = self.spots.iter().map(|spot| spot.quality).sum();
90 Some(total / self.spots.len() as f32)
91 }
92}