rs_pcd/decoder/compressed.rs
1// Copyright 2025 bigpear0201
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7// http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::error::{PcdError, Result};
16use crate::header::ValueType;
17use crate::layout::PcdLayout;
18use crate::storage::PointBlock;
19use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
20use lzf;
21use std::io::Read;
22
23pub struct CompressedReader<'a, R: Read> {
24 reader: &'a mut R,
25 layout: &'a PcdLayout,
26 points_to_read: usize,
27}
28
29impl<'a, R: Read> CompressedReader<'a, R> {
30 pub fn new(reader: &'a mut R, layout: &'a PcdLayout, points_to_read: usize) -> Self {
31 Self {
32 reader,
33 layout,
34 points_to_read,
35 }
36 }
37
38 pub fn decode(&mut self, output: &mut PointBlock) -> Result<()> {
39 let compressed_size = self.reader.read_u32::<LittleEndian>()? as usize;
40 let uncompressed_size = self.reader.read_u32::<LittleEndian>()? as usize;
41
42 let mut compressed_data = vec![0u8; compressed_size];
43 self.reader.read_exact(&mut compressed_data)?;
44
45 // Decompress
46 let decompressed = lzf::decompress(&compressed_data, uncompressed_size)
47 .map_err(|e| PcdError::Decompression(format!("{:?}", e)))?;
48
49 if decompressed.len() != uncompressed_size {
50 return Err(PcdError::Decompression(format!(
51 "Size mismatch: expected {}, got {}",
52 uncompressed_size,
53 decompressed.len()
54 )));
55 }
56
57 // Validate buffer size against layout
58 // SoA layout: sum(field.count * sizeof(type) * num_points)
59 // Wait, layout.total_size is per-point size (stride).
60 // Total bytes should be layout.total_size * points_to_read.
61 let expected_bytes = self.layout.total_size * self.points_to_read;
62 if uncompressed_size != expected_bytes {
63 return Err(PcdError::LayoutMismatch {
64 expected: expected_bytes,
65 got: uncompressed_size,
66 });
67 }
68
69 // Initialize output - verify all columns exist
70 for field in &self.layout.fields {
71 if output.get_column(&field.name).is_none() {
72 // Schema mismatch - column doesn't exist
73 // For now we just skip the check and let get_column_mut fail below
74 }
75 }
76 output.resize(self.points_to_read);
77
78 // Process fields (SoA in buffer: [Field1 All Points][Field2 All Points]...)
79 let mut offset = 0;
80
81 for field in &self.layout.fields {
82 let col = output
83 .get_column_mut(&field.name)
84 .ok_or(PcdError::InvalidDataFormat(format!(
85 "Missing column {}",
86 field.name
87 )))?;
88
89 let bytes_per_element = field.element_size; // e.g. 4 for f32
90 let elements_per_point = field.count; // e.g. 1
91 let bytes_per_field_block =
92 bytes_per_element * elements_per_point * self.points_to_read;
93
94 let end = offset + bytes_per_field_block;
95 let data_slice = &decompressed[offset..end];
96 offset = end;
97
98 match field.type_ {
99 ValueType::U8 => {
100 let vec = col.as_u8_mut().unwrap();
101 vec.copy_from_slice(data_slice);
102 }
103 ValueType::F32 => {
104 let vec = col.as_f32_mut().unwrap();
105 // Efficient copy using unsafe cast if alignment permits, or safely
106 // Since standard lzf returns Vec<u8>, it might not be aligned to 4.
107 // We iterate.
108 // Optimizations: chunks_exact(4).
109
110 // We must fill target vec. Target vec is flat for all points.
111 // field.count > 1 means interleaved? No, SoA usually means:
112 // If count=1: x1, x2, x3...
113 // If count=3 (Normal): nx1, ny1, nz1, nx2, ny2... ?
114 // OR: nx1..nxN, ny1..nyN ?
115 // PCL spec: "The fields are stored sequentially... field_1_point_1, field_1_point_2... field_2_point_1..."
116 // Wait. "binary_compressed format... The data is stored in a column-major order."
117 // Does it mean field 1 for all points, then field 2?
118 // Yes. "stored sequentially by field".
119 // But if a field has count > 1 (e.g. viewpoint), how is it stored?
120 // "dimensions corresponding to the same field are stored contiguously".
121 // So if field is "normal" (count=3), is it nx1, ny1, nz1, nx2...?
122 // OR nx1..nxN, ny1..nyN?
123 // Usually PCL treats distinct Names as fields. "Normal" is usually split into "normal_x, normal_y, normal_z" in fields list.
124 // If one field has count > 1 (e.g. FPFH signature 33 floats), it is stored as struct?
125 // PCL generic: it is simply array of structs compressed? No.
126 // It says "reorganized to column array".
127 // I will assume for count > 1, it's (val_1_1, val_1_2... val_1_count), (val_2_1...)...
128 // i.e. The unit being transposed is the whole field value (array).
129 // So yes, loop over points, copy `count` elements.
130
131 let mut i = 0;
132 for chunk in data_slice.chunks_exact(4) {
133 vec[i] = LittleEndian::read_f32(chunk);
134 i += 1;
135 }
136 }
137 ValueType::F64 => {
138 let vec = col.as_f64_mut().unwrap();
139 let mut i = 0;
140 for chunk in data_slice.chunks_exact(8) {
141 vec[i] = LittleEndian::read_f64(chunk);
142 i += 1;
143 }
144 }
145 ValueType::U16 => {
146 let vec = col.as_u16_mut().unwrap();
147 let mut i = 0;
148 for chunk in data_slice.chunks_exact(2) {
149 vec[i] = LittleEndian::read_u16(chunk);
150 i += 1;
151 }
152 }
153 ValueType::U32 => {
154 let vec = col.as_u32_mut().unwrap();
155 let mut i = 0;
156 for chunk in data_slice.chunks_exact(4) {
157 vec[i] = LittleEndian::read_u32(chunk);
158 i += 1;
159 }
160 }
161 ValueType::I8 => {
162 let vec = col.as_i8_mut().unwrap();
163 // Safe conversion from u8 to i8
164 for (dest, &src) in vec.iter_mut().zip(data_slice.iter()) {
165 *dest = src as i8;
166 }
167 }
168 ValueType::I16 => {
169 let vec = col.as_i16_mut().unwrap();
170 let mut i = 0;
171 for chunk in data_slice.chunks_exact(2) {
172 vec[i] = LittleEndian::read_i16(chunk);
173 i += 1;
174 }
175 }
176 ValueType::I32 => {
177 let vec = col.as_i32_mut().unwrap();
178 let mut i = 0;
179 for chunk in data_slice.chunks_exact(4) {
180 vec[i] = LittleEndian::read_i32(chunk);
181 i += 1;
182 }
183 }
184 }
185 }
186
187 Ok(())
188 }
189}