Skip to main content

semantic_scene/scene/
mod.rs

1//! Semantic scene graph model.
2
3use std::{collections::HashMap, fmt, io::BufRead, path::Path};
4
5mod level;
6mod object;
7mod region;
8
9pub use level::SemanticLevel;
10pub use object::SemanticObject;
11pub use region::SemanticRegion;
12
13use crate::{Aabb, Dataset};
14
15/// Header count kind used while loading scene descriptors.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum ElementKind {
18    /// Image count from the descriptor header.
19    Images,
20    /// Panorama count from the descriptor header.
21    Panoramas,
22    /// Vertex count from the descriptor header.
23    Vertices,
24    /// Surface count from the descriptor header.
25    Surfaces,
26    /// Segment count from the descriptor header.
27    Segments,
28    /// Object count from the descriptor header.
29    Objects,
30    /// Category count from the descriptor header.
31    Categories,
32    /// Region count from the descriptor header.
33    Regions,
34    /// Portal count from the descriptor header.
35    Portals,
36    /// Level count from the descriptor header.
37    Levels,
38}
39
40impl ElementKind {
41    pub(crate) const fn as_str(self) -> &'static str {
42        match self {
43            Self::Images => "images",
44            Self::Panoramas => "panoramas",
45            Self::Vertices => "vertices",
46            Self::Surfaces => "surfaces",
47            Self::Segments => "segments",
48            Self::Objects => "objects",
49            Self::Categories => "categories",
50            Self::Regions => "regions",
51            Self::Portals => "portals",
52            Self::Levels => "levels",
53        }
54    }
55}
56
57/// Scene containing semantically annotated levels, regions, and objects.
58///
59/// A scene owns its levels, levels own their regions, and regions own their
60/// objects. Category payloads are dataset-specific generic types.
61#[derive(Debug, Clone)]
62pub struct SemanticScene<ObjectCategory, RegionCategory> {
63    pub(crate) name: String,
64    pub(crate) label: String,
65    pub(crate) counts: HashMap<&'static str, usize>,
66    pub(crate) aabb: Option<Aabb>,
67    pub(crate) levels: Vec<SemanticLevel<ObjectCategory, RegionCategory>>,
68}
69
70impl<ObjectCategory, RegionCategory> Default for SemanticScene<ObjectCategory, RegionCategory> {
71    fn default() -> Self {
72        Self {
73            name: String::new(),
74            label: String::new(),
75            counts: HashMap::new(),
76            aabb: None,
77            levels: Vec::new(),
78        }
79    }
80}
81
82impl<ObjectCategory, RegionCategory> SemanticScene<ObjectCategory, RegionCategory> {
83    /// Creates a scene from its nested model parts.
84    ///
85    /// The `levels` vector owns the full nested scene graph: levels contain
86    /// regions, and regions contain objects. `counts` stores descriptor header
87    /// counts by their dataset-specific names.
88    #[must_use]
89    pub fn from_parts(
90        name: impl Into<String>,
91        label: impl Into<String>,
92        counts: HashMap<&'static str, usize>,
93        aabb: Option<Aabb>,
94        levels: Vec<SemanticLevel<ObjectCategory, RegionCategory>>,
95    ) -> Self {
96        Self {
97            name: name.into(),
98            label: label.into(),
99            counts,
100            aabb,
101            levels,
102        }
103    }
104
105    /// Returns the scene name from the descriptor header.
106    #[must_use]
107    pub fn name(&self) -> &str {
108        &self.name
109    }
110
111    /// Returns the scene label from the descriptor header.
112    #[must_use]
113    pub fn label(&self) -> &str {
114        &self.label
115    }
116
117    /// Returns the scene axis-aligned bounding box.
118    ///
119    /// This is optional because a malformed or partially built scene may not
120    /// have a parsed header.
121    #[must_use]
122    pub const fn aabb(&self) -> Option<&Aabb> {
123        self.aabb.as_ref()
124    }
125
126    pub(crate) fn count(&self, element: ElementKind) -> Option<usize> {
127        self.counts.get(element.as_str()).copied()
128    }
129
130    /// Returns all levels in the scene.
131    #[must_use]
132    pub fn levels(&self) -> &[SemanticLevel<ObjectCategory, RegionCategory>] {
133        &self.levels
134    }
135
136    /// Returns all regions in the scene.
137    pub fn regions(&self) -> impl Iterator<Item = &SemanticRegion<ObjectCategory, RegionCategory>> {
138        self.levels.iter().flat_map(SemanticLevel::regions)
139    }
140
141    /// Returns all semantic objects in the scene.
142    pub fn objects(&self) -> impl Iterator<Item = &SemanticObject<ObjectCategory>> {
143        self.levels.iter().flat_map(SemanticLevel::objects)
144    }
145}
146
147impl<ObjectCategory, RegionCategory> fmt::Display
148    for SemanticScene<ObjectCategory, RegionCategory>
149{
150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151        if formatter.alternate() {
152            write!(
153                formatter,
154                "SemanticScene {:?}\n  label: {:?}\n  bounds: {}\n  counts: {}",
155                self.name,
156                self.label,
157                self.display_aabb(),
158                self.display_counts()
159            )
160        } else {
161            write!(
162                formatter,
163                "SemanticScene {:?}: {}, bounds=[{}]",
164                self.name,
165                self.display_counts(),
166                self.display_aabb()
167            )
168        }
169    }
170}
171
172impl SemanticScene<(), ()> {
173    /// Loads a scene from a path using a dataset implementation.
174    ///
175    /// # Errors
176    ///
177    /// Returns I/O errors from opening the file or dataset-specific parse and
178    /// validation failures.
179    pub fn load<D: Dataset>(
180        path: impl AsRef<Path>,
181        options: D::Options,
182    ) -> Result<SemanticScene<D::ObjectCategory, D::RegionCategory>, D::Error>
183    where
184        D::Error: From<std::io::Error>,
185    {
186        D::from_path(path, options)
187    }
188
189    /// Loads a scene from any buffered reader using a dataset implementation.
190    ///
191    /// # Errors
192    ///
193    /// Returns dataset-specific I/O, parse, and validation failures.
194    pub fn from_reader<D: Dataset, R: BufRead>(
195        reader: R,
196        options: D::Options,
197    ) -> Result<SemanticScene<D::ObjectCategory, D::RegionCategory>, D::Error> {
198        D::from_reader(reader, options)
199    }
200
201    /// Loads a scene from an in-memory string using a dataset implementation.
202    ///
203    /// # Errors
204    ///
205    /// Returns dataset-specific parse and validation failures.
206    pub fn from_str<D: Dataset>(
207        input: &str,
208        options: D::Options,
209    ) -> Result<SemanticScene<D::ObjectCategory, D::RegionCategory>, D::Error> {
210        D::from_str(input, options)
211    }
212}
213
214impl<ObjectCategory, RegionCategory> SemanticScene<ObjectCategory, RegionCategory> {
215    const fn display_aabb(&self) -> DisplayAabb<'_> {
216        DisplayAabb(self.aabb.as_ref())
217    }
218
219    const fn display_counts(&self) -> DisplayCounts<'_, ObjectCategory, RegionCategory> {
220        DisplayCounts(self)
221    }
222}
223
224struct DisplayAabb<'a>(Option<&'a Aabb>);
225
226impl fmt::Display for DisplayAabb<'_> {
227    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
228        match self.0 {
229            Some(aabb) => aabb.fmt(formatter),
230            None => formatter.write_str("none"),
231        }
232    }
233}
234
235struct DisplayCounts<'a, ObjectCategory, RegionCategory>(
236    &'a SemanticScene<ObjectCategory, RegionCategory>,
237);
238
239impl<ObjectCategory, RegionCategory> fmt::Display
240    for DisplayCounts<'_, ObjectCategory, RegionCategory>
241{
242    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
243        write!(
244            formatter,
245            "levels={}, regions={}, objects={}, categories={}, segments={}",
246            self.0.levels.len(),
247            self.0.regions().count(),
248            self.0.objects().count(),
249            self.0.count(ElementKind::Categories).unwrap_or(0),
250            self.0.count(ElementKind::Segments).unwrap_or(0),
251        )
252    }
253}