semantic-scene 0.1.1

Rust parser for semantic scene descriptors, currently focused on Habitat-Sim Matterport3D .house files.
Documentation
//! Semantic scene graph model.

use std::{collections::HashMap, fmt, io::BufRead, path::Path};

mod level;
mod object;
mod region;

pub use level::SemanticLevel;
pub use object::SemanticObject;
pub use region::SemanticRegion;

use crate::{Aabb, Dataset};

/// Header count kind used while loading scene descriptors.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ElementKind {
    /// Image count from the descriptor header.
    Images,
    /// Panorama count from the descriptor header.
    Panoramas,
    /// Vertex count from the descriptor header.
    Vertices,
    /// Surface count from the descriptor header.
    Surfaces,
    /// Segment count from the descriptor header.
    Segments,
    /// Object count from the descriptor header.
    Objects,
    /// Category count from the descriptor header.
    Categories,
    /// Region count from the descriptor header.
    Regions,
    /// Portal count from the descriptor header.
    Portals,
    /// Level count from the descriptor header.
    Levels,
}

impl ElementKind {
    pub(crate) const fn as_str(self) -> &'static str {
        match self {
            Self::Images => "images",
            Self::Panoramas => "panoramas",
            Self::Vertices => "vertices",
            Self::Surfaces => "surfaces",
            Self::Segments => "segments",
            Self::Objects => "objects",
            Self::Categories => "categories",
            Self::Regions => "regions",
            Self::Portals => "portals",
            Self::Levels => "levels",
        }
    }
}

/// Scene containing semantically annotated levels, regions, and objects.
///
/// A scene owns its levels, levels own their regions, and regions own their
/// objects. Category payloads are dataset-specific generic types.
#[derive(Debug, Clone)]
pub struct SemanticScene<ObjectCategory, RegionCategory> {
    pub(crate) name: String,
    pub(crate) label: String,
    pub(crate) counts: HashMap<&'static str, usize>,
    pub(crate) aabb: Option<Aabb>,
    pub(crate) levels: Vec<SemanticLevel<ObjectCategory, RegionCategory>>,
}

impl<ObjectCategory, RegionCategory> Default for SemanticScene<ObjectCategory, RegionCategory> {
    fn default() -> Self {
        Self {
            name: String::new(),
            label: String::new(),
            counts: HashMap::new(),
            aabb: None,
            levels: Vec::new(),
        }
    }
}

impl<ObjectCategory, RegionCategory> SemanticScene<ObjectCategory, RegionCategory> {
    /// Creates a scene from its nested model parts.
    ///
    /// The `levels` vector owns the full nested scene graph: levels contain
    /// regions, and regions contain objects. `counts` stores descriptor header
    /// counts by their dataset-specific names.
    #[must_use]
    pub fn from_parts(
        name: impl Into<String>,
        label: impl Into<String>,
        counts: HashMap<&'static str, usize>,
        aabb: Option<Aabb>,
        levels: Vec<SemanticLevel<ObjectCategory, RegionCategory>>,
    ) -> Self {
        Self {
            name: name.into(),
            label: label.into(),
            counts,
            aabb,
            levels,
        }
    }

    /// Returns the scene name from the descriptor header.
    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the scene label from the descriptor header.
    #[must_use]
    pub fn label(&self) -> &str {
        &self.label
    }

    /// Returns the scene axis-aligned bounding box.
    ///
    /// This is optional because a malformed or partially built scene may not
    /// have a parsed header.
    #[must_use]
    pub const fn aabb(&self) -> Option<&Aabb> {
        self.aabb.as_ref()
    }

    pub(crate) fn count(&self, element: ElementKind) -> Option<usize> {
        self.counts.get(element.as_str()).copied()
    }

    /// Returns all levels in the scene.
    #[must_use]
    pub fn levels(&self) -> &[SemanticLevel<ObjectCategory, RegionCategory>] {
        &self.levels
    }

    /// Returns all regions in the scene.
    pub fn regions(&self) -> impl Iterator<Item = &SemanticRegion<ObjectCategory, RegionCategory>> {
        self.levels.iter().flat_map(SemanticLevel::regions)
    }

    /// Returns all semantic objects in the scene.
    pub fn objects(&self) -> impl Iterator<Item = &SemanticObject<ObjectCategory>> {
        self.levels.iter().flat_map(SemanticLevel::objects)
    }
}

impl<ObjectCategory, RegionCategory> fmt::Display
    for SemanticScene<ObjectCategory, RegionCategory>
{
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        if formatter.alternate() {
            write!(
                formatter,
                "SemanticScene {:?}\n  label: {:?}\n  bounds: {}\n  counts: {}",
                self.name,
                self.label,
                self.display_aabb(),
                self.display_counts()
            )
        } else {
            write!(
                formatter,
                "SemanticScene {:?}: {}, bounds=[{}]",
                self.name,
                self.display_counts(),
                self.display_aabb()
            )
        }
    }
}

impl SemanticScene<(), ()> {
    /// Loads a scene from a path using a dataset implementation.
    ///
    /// # Errors
    ///
    /// Returns I/O errors from opening the file or dataset-specific parse and
    /// validation failures.
    pub fn load<D: Dataset>(
        path: impl AsRef<Path>,
        options: D::Options,
    ) -> Result<SemanticScene<D::ObjectCategory, D::RegionCategory>, D::Error>
    where
        D::Error: From<std::io::Error>,
    {
        D::from_path(path, options)
    }

    /// Loads a scene from any buffered reader using a dataset implementation.
    ///
    /// # Errors
    ///
    /// Returns dataset-specific I/O, parse, and validation failures.
    pub fn from_reader<D: Dataset, R: BufRead>(
        reader: R,
        options: D::Options,
    ) -> Result<SemanticScene<D::ObjectCategory, D::RegionCategory>, D::Error> {
        D::from_reader(reader, options)
    }

    /// Loads a scene from an in-memory string using a dataset implementation.
    ///
    /// # Errors
    ///
    /// Returns dataset-specific parse and validation failures.
    pub fn from_str<D: Dataset>(
        input: &str,
        options: D::Options,
    ) -> Result<SemanticScene<D::ObjectCategory, D::RegionCategory>, D::Error> {
        D::from_str(input, options)
    }
}

impl<ObjectCategory, RegionCategory> SemanticScene<ObjectCategory, RegionCategory> {
    const fn display_aabb(&self) -> DisplayAabb<'_> {
        DisplayAabb(self.aabb.as_ref())
    }

    const fn display_counts(&self) -> DisplayCounts<'_, ObjectCategory, RegionCategory> {
        DisplayCounts(self)
    }
}

struct DisplayAabb<'a>(Option<&'a Aabb>);

impl fmt::Display for DisplayAabb<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.0 {
            Some(aabb) => aabb.fmt(formatter),
            None => formatter.write_str("none"),
        }
    }
}

struct DisplayCounts<'a, ObjectCategory, RegionCategory>(
    &'a SemanticScene<ObjectCategory, RegionCategory>,
);

impl<ObjectCategory, RegionCategory> fmt::Display
    for DisplayCounts<'_, ObjectCategory, RegionCategory>
{
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "levels={}, regions={}, objects={}, categories={}, segments={}",
            self.0.levels.len(),
            self.0.regions().count(),
            self.0.objects().count(),
            self.0.count(ElementKind::Categories).unwrap_or(0),
            self.0.count(ElementKind::Segments).unwrap_or(0),
        )
    }
}