lumen_geometry/colon/
segments.rs

1//! Anatomical segments of the colon.
2
3use crate::AnatomicalSegment;
4
5/// The eight anatomical regions of the colon.
6///
7/// Listed in order from entry (rectum) to terminus (cecum), matching
8/// the direction of colonoscopy navigation.
9///
10/// ```text
11/// t=0.0                                                    t=1.0
12///   │                                                        │
13///   ▼                                                        ▼
14/// Rectum → Sigmoid → Descending → Splenic → Transverse → Hepatic → Ascending → Cecum
15/// ```
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum ColonSegment {
18    /// Entry point, relatively straight. (t ≈ 0.00-0.08)
19    Rectum,
20    /// S-shaped curve, highly variable between individuals. (t ≈ 0.08-0.20)
21    Sigmoid,
22    /// Runs down the left side of the abdomen. (t ≈ 0.20-0.35)
23    Descending,
24    /// Sharp ~90° bend near the spleen. (t ≈ 0.35-0.40)
25    SplenicFlexure,
26    /// Crosses the abdomen horizontally. (t ≈ 0.40-0.65)
27    Transverse,
28    /// Sharp ~90° bend near the liver. (t ≈ 0.65-0.70)
29    HepaticFlexure,
30    /// Runs up the right side of the abdomen. (t ≈ 0.70-0.90)
31    Ascending,
32    /// Pouch at terminus, connects to small intestine. (t ≈ 0.90-1.00)
33    Cecum,
34}
35
36impl ColonSegment {
37    /// All segments in anatomical order (rectum to cecum).
38    pub const ALL: [ColonSegment; 8] = [
39        ColonSegment::Rectum,
40        ColonSegment::Sigmoid,
41        ColonSegment::Descending,
42        ColonSegment::SplenicFlexure,
43        ColonSegment::Transverse,
44        ColonSegment::HepaticFlexure,
45        ColonSegment::Ascending,
46        ColonSegment::Cecum,
47    ];
48
49    /// Default t-parameter boundaries for each segment.
50    ///
51    /// These are approximate values based on average adult anatomy.
52    /// Actual boundaries vary with `ColonConfig` seed.
53    pub const DEFAULT_BOUNDARIES: [f32; 9] = [
54        0.00, // Rectum start
55        0.08, // Sigmoid start
56        0.20, // Descending start
57        0.35, // Splenic Flexure start
58        0.40, // Transverse start
59        0.65, // Hepatic Flexure start
60        0.70, // Ascending start
61        0.90, // Cecum start
62        1.00, // End
63    ];
64
65    /// Get the segment containing parameter t.
66    ///
67    /// Uses default boundaries. For seed-varied boundaries, use
68    /// `ColonCurve::segment_at()`.
69    pub fn at_t(t: f32) -> Self {
70        let t = t.clamp(0.0, 1.0);
71        for (i, segment) in Self::ALL.iter().enumerate() {
72            if t < Self::DEFAULT_BOUNDARIES[i + 1] {
73                return *segment;
74            }
75        }
76        ColonSegment::Cecum
77    }
78
79    /// Index of this segment (0 = Rectum, 7 = Cecum).
80    pub fn index(&self) -> usize {
81        match self {
82            ColonSegment::Rectum => 0,
83            ColonSegment::Sigmoid => 1,
84            ColonSegment::Descending => 2,
85            ColonSegment::SplenicFlexure => 3,
86            ColonSegment::Transverse => 4,
87            ColonSegment::HepaticFlexure => 5,
88            ColonSegment::Ascending => 6,
89            ColonSegment::Cecum => 7,
90        }
91    }
92
93    /// Whether this segment is a flexure (sharp bend).
94    pub fn is_flexure(&self) -> bool {
95        matches!(
96            self,
97            ColonSegment::SplenicFlexure | ColonSegment::HepaticFlexure
98        )
99    }
100}
101
102impl AnatomicalSegment for ColonSegment {
103    fn name(&self) -> &'static str {
104        match self {
105            ColonSegment::Rectum => "Rectum",
106            ColonSegment::Sigmoid => "Sigmoid",
107            ColonSegment::Descending => "Descending",
108            ColonSegment::SplenicFlexure => "Splenic Flexure",
109            ColonSegment::Transverse => "Transverse",
110            ColonSegment::HepaticFlexure => "Hepatic Flexure",
111            ColonSegment::Ascending => "Ascending",
112            ColonSegment::Cecum => "Cecum",
113        }
114    }
115
116    fn t_range(&self) -> (f32, f32) {
117        let idx = self.index();
118        (
119            ColonSegment::DEFAULT_BOUNDARIES[idx],
120            ColonSegment::DEFAULT_BOUNDARIES[idx + 1],
121        )
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn segment_at_t_boundaries() {
131        assert_eq!(ColonSegment::at_t(0.0), ColonSegment::Rectum);
132        assert_eq!(ColonSegment::at_t(0.07), ColonSegment::Rectum);
133        assert_eq!(ColonSegment::at_t(0.08), ColonSegment::Sigmoid);
134        assert_eq!(ColonSegment::at_t(0.5), ColonSegment::Transverse);
135        assert_eq!(ColonSegment::at_t(1.0), ColonSegment::Cecum);
136    }
137
138    #[test]
139    fn segment_at_t_clamping() {
140        assert_eq!(ColonSegment::at_t(-0.5), ColonSegment::Rectum);
141        assert_eq!(ColonSegment::at_t(1.5), ColonSegment::Cecum);
142    }
143
144    #[test]
145    fn all_segments_cover_full_range() {
146        // Verify boundaries are contiguous
147        for i in 0..8 {
148            let (start, end) = ColonSegment::ALL[i].t_range();
149            assert_eq!(start, ColonSegment::DEFAULT_BOUNDARIES[i]);
150            assert_eq!(end, ColonSegment::DEFAULT_BOUNDARIES[i + 1]);
151        }
152    }
153
154    #[test]
155    fn flexures_identified() {
156        assert!(!ColonSegment::Rectum.is_flexure());
157        assert!(!ColonSegment::Transverse.is_flexure());
158        assert!(ColonSegment::SplenicFlexure.is_flexure());
159        assert!(ColonSegment::HepaticFlexure.is_flexure());
160    }
161}