diff --git a/lidarserv-input-file/src/commands/replay.rs b/lidarserv-input-file/src/commands/replay.rs index ccc64245..adb5784f 100644 --- a/lidarserv-input-file/src/commands/replay.rs +++ b/lidarserv-input-file/src/commands/replay.rs @@ -4,6 +4,7 @@ use lidarserv_common::geometry::coordinate_system::CoordinateSystem; use lidarserv_common::tracy_client::{plot, span}; use lidarserv_input_file::extractors::AttributeExtractor; use lidarserv_input_file::extractors::basic_flags::LasBasicFlagsExtractor; +use lidarserv_input_file::extractors::color::ColorExtractor; use lidarserv_input_file::extractors::copy::CopyExtractor; use lidarserv_input_file::extractors::extended_flags::LasExtendedFlagsExtractor; use lidarserv_input_file::extractors::position::PositionExtractor; @@ -238,6 +239,14 @@ impl FrameManager { continue; } + // color + if let Some(extractor) = + ColorExtractor::check(dst_attribute, dst_point_size, src_layout) + { + extractors.push(Box::new(extractor)); + continue; + } + // most normal attributes: bitwise copy if let Some(extractor) = CopyExtractor::check(dst_attribute, dst_point_size, src_layout) { diff --git a/lidarserv-input-file/src/extractors/color.rs b/lidarserv-input-file/src/extractors/color.rs new file mode 100644 index 00000000..8d195f90 --- /dev/null +++ b/lidarserv-input-file/src/extractors/color.rs @@ -0,0 +1,159 @@ +use pasture_core::layout::attributes::COLOR_RGB; +use pasture_core::layout::{PointAttributeDataType, PointAttributeMember, PointLayout}; + +use super::AttributeExtractor; + +pub struct ColorExtractor { + src_offset: usize, + src_stride: usize, + dst_offset: usize, + dst_stride: usize, + conversion: Conversion, +} + +enum Conversion { + Copy { len: usize }, + Vec3u16ToVec3u8, + U32ToVec3u8, + U64ToVec3u16, +} + +impl ColorExtractor { + pub fn check( + dst_attribute: &PointAttributeMember, + dst_point_size: usize, + src_layout: &PointLayout, + ) -> Option { + if !is_color_attribute(dst_attribute) { + return None; + } + + let dst_datatype = dst_attribute.datatype(); + if !matches!( + dst_datatype, + PointAttributeDataType::Vec3u8 | PointAttributeDataType::Vec3u16 + ) { + return None; + } + + let src_attribute = src_layout + .get_attribute(dst_attribute.attribute_definition()) + .or_else(|| src_layout.get_attribute_by_name(dst_attribute.name())) + .or_else(|| { + let dst_name = dst_attribute.name().to_ascii_lowercase(); + src_layout + .attributes() + .find(|attr| attr.name().to_ascii_lowercase() == dst_name) + })?; + + let conversion = match (src_attribute.datatype(), dst_datatype) { + (PointAttributeDataType::Vec3u8, PointAttributeDataType::Vec3u8) => { + Conversion::Copy { + len: dst_attribute.size() as usize, + } + } + (PointAttributeDataType::Vec3u16, PointAttributeDataType::Vec3u16) => { + Conversion::Copy { + len: dst_attribute.size() as usize, + } + } + (PointAttributeDataType::Vec3u16, PointAttributeDataType::Vec3u8) => { + Conversion::Vec3u16ToVec3u8 + } + (PointAttributeDataType::U32, PointAttributeDataType::Vec3u8) => { + Conversion::U32ToVec3u8 + } + (PointAttributeDataType::U64, PointAttributeDataType::Vec3u16) => { + Conversion::U64ToVec3u16 + } + _ => return None, + }; + + Some(ColorExtractor { + src_offset: src_attribute.byte_range_within_point().start, + src_stride: src_layout.size_of_point_entry() as usize, + dst_offset: dst_attribute.byte_range_within_point().start, + dst_stride: dst_point_size, + conversion, + }) + } +} + +impl AttributeExtractor for ColorExtractor { + fn extract(&self, src: &[u8], dst: &mut [u8]) { + let nr_points = src.len() / self.src_stride; + assert_eq!(src.len(), nr_points * self.src_stride); + assert_eq!(dst.len(), nr_points * self.dst_stride); + + match self.conversion { + Conversion::Copy { len } => { + for i in 0..nr_points { + let src_start = i * self.src_stride + self.src_offset; + let dst_start = i * self.dst_stride + self.dst_offset; + dst[dst_start..dst_start + len] + .copy_from_slice(&src[src_start..src_start + len]); + } + } + Conversion::Vec3u16ToVec3u8 => { + for i in 0..nr_points { + let src_start = i * self.src_stride + self.src_offset; + let dst_start = i * self.dst_stride + self.dst_offset; + for c in 0..3 { + let s = src_start + c * 2; + let value = u16::from_le_bytes([src[s], src[s + 1]]); + dst[dst_start + c] = value as u8; + } + } + } + Conversion::U32ToVec3u8 => { + for i in 0..nr_points { + let src_start = i * self.src_stride + self.src_offset; + let dst_start = i * self.dst_stride + self.dst_offset; + let bytes = [ + src[src_start], + src[src_start + 1], + src[src_start + 2], + src[src_start + 3], + ]; + let packed = u32::from_le_bytes(bytes); + dst[dst_start] = (packed & 0xFF) as u8; + dst[dst_start + 1] = ((packed >> 8) & 0xFF) as u8; + dst[dst_start + 2] = ((packed >> 16) & 0xFF) as u8; + } + } + Conversion::U64ToVec3u16 => { + for i in 0..nr_points { + let src_start = i * self.src_stride + self.src_offset; + let dst_start = i * self.dst_stride + self.dst_offset; + let bytes = [ + src[src_start], + src[src_start + 1], + src[src_start + 2], + src[src_start + 3], + src[src_start + 4], + src[src_start + 5], + src[src_start + 6], + src[src_start + 7], + ]; + let packed = u64::from_le_bytes(bytes); + for c in 0..3 { + let value = ((packed >> (c * 16)) & 0xFFFF) as u16; + let le = value.to_le_bytes(); + let dst_idx = dst_start + c * 2; + dst[dst_idx] = le[0]; + dst[dst_idx + 1] = le[1]; + } + } + } + } + } +} + +fn is_color_attribute(attribute: &PointAttributeMember) -> bool { + if *attribute.attribute_definition() == COLOR_RGB { + return true; + } + + let name = attribute.name().to_ascii_lowercase(); + name.contains("color") || name.ends_with("rgb") || name == "rgb" +} diff --git a/lidarserv-input-file/src/extractors/mod.rs b/lidarserv-input-file/src/extractors/mod.rs index 25331aea..18777cb8 100644 --- a/lidarserv-input-file/src/extractors/mod.rs +++ b/lidarserv-input-file/src/extractors/mod.rs @@ -15,6 +15,7 @@ pub mod scan_angle; pub mod scan_angle_rank; pub mod scan_direction_flag; pub mod scanner_channel; +pub mod color; pub trait AttributeExtractor { fn extract(&self, src: &[u8], dst: &mut [u8]); diff --git a/lidarserv-server/src/index/settings.rs b/lidarserv-server/src/index/settings.rs index 04bc34ae..3f696eef 100644 --- a/lidarserv-server/src/index/settings.rs +++ b/lidarserv-server/src/index/settings.rs @@ -43,7 +43,17 @@ fn get_data_folder_settings_file(path: &Path) -> PathBuf { impl IndexSettings { pub fn load_from_file(file_name: &Path) -> Result { let file = File::open(file_name)?; - let settings = serde_json::from_reader(file)?; + let mut settings: IndexSettings = serde_json::from_reader(file)?; + + // Rebuild the point layout from the attribute definitions to eliminate + // stale offset/size metadata lingering in older settings files. + let attributes: Vec<_> = settings + .point_layout + .attributes() + .map(|member| member.attribute_definition().clone()) + .collect(); + settings.point_layout = PointLayout::from_attributes(&attributes); + Ok(settings) }