Skip to main content

polyscope_structures/floating/
scalar_image.rs

1//! Floating scalar image quantity.
2
3use polyscope_core::quantity::{Quantity, QuantityKind};
4
5use super::ImageOrigin;
6
7/// A floating scalar image quantity (not attached to any structure).
8///
9/// Displays a 2D grid of scalar values using a colormap.
10pub struct FloatingScalarImage {
11    name: String,
12    width: u32,
13    height: u32,
14    values: Vec<f32>,
15    origin: ImageOrigin,
16    enabled: bool,
17    colormap_name: String,
18    data_min: f32,
19    data_max: f32,
20}
21
22impl FloatingScalarImage {
23    /// Creates a new floating scalar image.
24    pub fn new(name: impl Into<String>, width: u32, height: u32, values: Vec<f32>) -> Self {
25        let min = values.iter().copied().fold(f32::INFINITY, f32::min);
26        let max = values.iter().copied().fold(f32::NEG_INFINITY, f32::max);
27
28        Self {
29            name: name.into(),
30            width,
31            height,
32            values,
33            origin: ImageOrigin::default(),
34            enabled: true,
35            colormap_name: "viridis".to_string(),
36            data_min: min,
37            data_max: max,
38        }
39    }
40
41    /// Returns the image width.
42    #[must_use]
43    pub fn width(&self) -> u32 {
44        self.width
45    }
46
47    /// Returns the image height.
48    #[must_use]
49    pub fn height(&self) -> u32 {
50        self.height
51    }
52
53    /// Returns the scalar values.
54    #[must_use]
55    pub fn values(&self) -> &[f32] {
56        &self.values
57    }
58
59    /// Gets the image origin.
60    #[must_use]
61    pub fn origin(&self) -> ImageOrigin {
62        self.origin
63    }
64
65    /// Sets the image origin.
66    pub fn set_origin(&mut self, origin: ImageOrigin) -> &mut Self {
67        self.origin = origin;
68        self
69    }
70
71    /// Gets the colormap name.
72    #[must_use]
73    pub fn colormap_name(&self) -> &str {
74        &self.colormap_name
75    }
76
77    /// Sets the colormap name.
78    pub fn set_colormap(&mut self, name: impl Into<String>) -> &mut Self {
79        self.colormap_name = name.into();
80        self
81    }
82
83    /// Gets the data range minimum.
84    #[must_use]
85    pub fn data_min(&self) -> f32 {
86        self.data_min
87    }
88
89    /// Gets the data range maximum.
90    #[must_use]
91    pub fn data_max(&self) -> f32 {
92        self.data_max
93    }
94
95    /// Sets the data range.
96    pub fn set_data_range(&mut self, min: f32, max: f32) -> &mut Self {
97        self.data_min = min;
98        self.data_max = max;
99        self
100    }
101
102    /// Returns the pixel value at (x, y), accounting for image origin.
103    #[must_use]
104    pub fn pixel(&self, x: u32, y: u32) -> f32 {
105        let row = match self.origin {
106            ImageOrigin::UpperLeft => y,
107            ImageOrigin::LowerLeft => self.height - 1 - y,
108        };
109        self.values[(row * self.width + x) as usize]
110    }
111}
112
113impl Quantity for FloatingScalarImage {
114    fn as_any(&self) -> &dyn std::any::Any {
115        self
116    }
117    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
118        self
119    }
120    fn name(&self) -> &str {
121        &self.name
122    }
123    #[allow(clippy::unnecessary_literal_bound)]
124    fn structure_name(&self) -> &str {
125        "" // No parent structure
126    }
127    fn kind(&self) -> QuantityKind {
128        QuantityKind::Other
129    }
130    fn is_enabled(&self) -> bool {
131        self.enabled
132    }
133    fn set_enabled(&mut self, enabled: bool) {
134        self.enabled = enabled;
135    }
136    fn build_ui(&mut self, _ui: &dyn std::any::Any) {}
137    fn refresh(&mut self) {}
138    fn data_size(&self) -> usize {
139        self.values.len()
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_scalar_image_creation() {
149        let values = vec![0.0, 0.5, 1.0, 1.5];
150        let img = FloatingScalarImage::new("test", 2, 2, values);
151
152        assert_eq!(img.name(), "test");
153        assert_eq!(img.width(), 2);
154        assert_eq!(img.height(), 2);
155        assert_eq!(img.data_size(), 4);
156        assert_eq!(img.data_min(), 0.0);
157        assert_eq!(img.data_max(), 1.5);
158        assert_eq!(img.kind(), QuantityKind::Other);
159        assert!(img.is_enabled());
160    }
161
162    #[test]
163    fn test_scalar_image_pixel_access() {
164        // 2x2 image: [0, 1, 2, 3] row-major
165        let values = vec![0.0, 1.0, 2.0, 3.0];
166        let img = FloatingScalarImage::new("test", 2, 2, values);
167
168        // UpperLeft (default): row 0 = top
169        assert_eq!(img.pixel(0, 0), 0.0); // top-left
170        assert_eq!(img.pixel(1, 0), 1.0); // top-right
171        assert_eq!(img.pixel(0, 1), 2.0); // bottom-left
172        assert_eq!(img.pixel(1, 1), 3.0); // bottom-right
173    }
174
175    #[test]
176    fn test_scalar_image_lower_left_origin() {
177        let values = vec![0.0, 1.0, 2.0, 3.0];
178        let mut img = FloatingScalarImage::new("test", 2, 2, values);
179        img.set_origin(ImageOrigin::LowerLeft);
180
181        // LowerLeft: row 0 = bottom, so (0,0) maps to bottom-left = values[2]
182        assert_eq!(img.pixel(0, 0), 2.0); // y=0 is bottom row
183        assert_eq!(img.pixel(1, 0), 3.0);
184        assert_eq!(img.pixel(0, 1), 0.0); // y=1 is top row
185        assert_eq!(img.pixel(1, 1), 1.0);
186    }
187
188    #[test]
189    fn test_scalar_image_setters() {
190        let mut img = FloatingScalarImage::new("test", 2, 2, vec![0.0; 4]);
191
192        img.set_colormap("blues");
193        assert_eq!(img.colormap_name(), "blues");
194
195        img.set_data_range(-1.0, 1.0);
196        assert_eq!(img.data_min(), -1.0);
197        assert_eq!(img.data_max(), 1.0);
198    }
199}