e57/
images.rs

1use crate::xml;
2use crate::{Blob, DateTime, Error, Result, Transform};
3use roxmltree::{Document, Node};
4use std::f64::consts::PI;
5
6/// Descriptor with metadata for a single image.
7#[derive(Clone, Debug)]
8#[non_exhaustive]
9pub struct Image {
10    /// Globally unique identifier for the image.
11    /// Required by spec, but the reference C++ implementation does allow to omit this field, so we do too.
12    pub guid: Option<String>,
13    /// Preview/illustration image without a projection model.
14    pub visual_reference: Option<VisualReferenceImage>,
15    /// Image with one of the supported projection models.
16    pub projection: Option<Projection>,
17    /// Transforms the local coordinate system of the image to the file-level coordinate system.
18    pub transform: Option<Transform>,
19    /// GUID of the point cloud that was captured with this image.
20    pub pointcloud_guid: Option<String>,
21    /// User-defined name for the image.
22    pub name: Option<String>,
23    /// User-defined description for the image.
24    pub description: Option<String>,
25    /// Date and time when this image was captured.
26    pub acquisition: Option<DateTime>,
27    /// The name of the manufacturer for the sensor used to capture the image.
28    pub sensor_vendor: Option<String>,
29    /// The model name or number for the sensor used to capture the image.
30    pub sensor_model: Option<String>,
31    /// The serial number of the sensor used to capture the image.
32    pub sensor_serial: Option<String>,
33}
34
35impl Image {
36    fn from_node(node: &Node) -> Result<Self> {
37        let guid = xml::opt_string(node, "guid")?;
38        let pointcloud_guid = xml::opt_string(node, "associatedData3DGuid")?;
39        let transform = xml::opt_transform(node, "pose")?;
40        let name = xml::opt_string(node, "name")?;
41        let description = xml::opt_string(node, "description")?;
42        let sensor_model = xml::opt_string(node, "sensorModel")?;
43        let sensor_vendor = xml::opt_string(node, "sensorVendor")?;
44        let sensor_serial = xml::opt_string(node, "sensorSerialNumber")?;
45        let acquisition = xml::opt_date_time(node, "acquisitionDateTime")?;
46        let projection = Projection::from_image_node(node)?;
47
48        let visual_reference_node = node
49            .children()
50            .find(|n| n.has_tag_name("visualReferenceRepresentation"));
51        let visual_reference = if let Some(node) = visual_reference_node {
52            Some(VisualReferenceImage::from_node(&node)?)
53        } else {
54            None
55        };
56
57        Ok(Self {
58            guid,
59            pointcloud_guid,
60            transform,
61            name,
62            description,
63            acquisition,
64            sensor_vendor,
65            sensor_model,
66            sensor_serial,
67            projection,
68            visual_reference,
69        })
70    }
71
72    pub(crate) fn vec_from_document(document: &Document) -> Result<Vec<Self>> {
73        let mut images = Vec::new();
74        if let Some(images2d_node) = document.descendants().find(|n| n.has_tag_name("images2D")) {
75            for n in images2d_node.children() {
76                if n.has_tag_name("vectorChild") && n.attribute("type") == Some("Structure") {
77                    let image = Self::from_node(&n)?;
78                    images.push(image);
79                }
80            }
81        }
82        Ok(images)
83    }
84
85    pub(crate) fn xml_string(&self) -> String {
86        let mut xml = String::new();
87        xml += "<vectorChild type=\"Structure\">\n";
88        if let Some(guid) = &self.guid {
89            xml += &xml::gen_string("guid", &guid);
90        }
91        if let Some(vis_ref) = &self.visual_reference {
92            xml += &vis_ref.xml_string();
93        }
94        if let Some(rep) = &self.projection {
95            xml += &rep.xml_string();
96        }
97        if let Some(trans) = &self.transform {
98            xml += &trans.xml_string("pose");
99        }
100        if let Some(pc_guid) = &self.pointcloud_guid {
101            xml += &xml::gen_string("associatedData3DGuid", &pc_guid);
102        }
103        if let Some(name) = &self.name {
104            xml += &xml::gen_string("name", &name);
105        }
106        if let Some(desc) = &self.description {
107            xml += &xml::gen_string("description", &desc);
108        }
109        if let Some(acquisition) = &self.acquisition {
110            xml += &acquisition.xml_string("acquisitionDateTime");
111        }
112        if let Some(vendor) = &self.sensor_vendor {
113            xml += &xml::gen_string("sensorVendor", &vendor);
114        }
115        if let Some(model) = &self.sensor_model {
116            xml += &xml::gen_string("sensorModel", &model);
117        }
118        if let Some(serial) = &self.sensor_serial {
119            xml += &xml::gen_string("sensorSerialNumber", &serial);
120        }
121        xml += "</vectorChild>\n";
122        xml
123    }
124}
125
126/// Contains one of the tree possible types for projectable images.
127#[derive(Debug, Clone)]
128pub enum Projection {
129    /// Image with a pinhole projection model.
130    Pinhole(PinholeImage),
131    /// Image with a spherical projection model.
132    Spherical(SphericalImage),
133    /// Image with a cylindrical projection model.
134    Cylindrical(CylindricalImage),
135}
136
137impl Projection {
138    pub(crate) fn from_image_node(image_node: &Node) -> Result<Option<Self>> {
139        let pinhole = image_node
140            .children()
141            .find(|n| n.has_tag_name("pinholeRepresentation"));
142        if let Some(node) = &pinhole {
143            return Ok(Some(Self::Pinhole(PinholeImage::from_node(node)?)));
144        }
145
146        let spherical = image_node
147            .children()
148            .find(|n| n.has_tag_name("sphericalRepresentation"));
149        if let Some(node) = &spherical {
150            return Ok(Some(Self::Spherical(SphericalImage::from_node(node)?)));
151        }
152
153        let cylindrical = image_node
154            .children()
155            .find(|n| n.has_tag_name("cylindricalRepresentation"));
156        if let Some(node) = &cylindrical {
157            return Ok(Some(Self::Cylindrical(CylindricalImage::from_node(node)?)));
158        }
159
160        Ok(None)
161    }
162
163    pub(crate) fn xml_string(&self) -> String {
164        match self {
165            Projection::Pinhole(p) => p.xml_string(),
166            Projection::Spherical(s) => s.xml_string(),
167            Projection::Cylindrical(c) => c.xml_string(),
168        }
169    }
170}
171
172/// File format of an image stored inside the E57 file as blob.
173#[derive(Debug, Clone)]
174pub enum ImageFormat {
175    /// Portable Network Graphics (PNG) image format.
176    Png,
177    /// JPEG File Interchange Format (JFIF) image format.
178    Jpeg,
179}
180
181/// Contains a blob with image data and the corresponding file type.
182#[derive(Debug, Clone)]
183#[non_exhaustive]
184pub struct ImageBlob {
185    /// Descriptor for the binary blob of the image.
186    pub data: Blob,
187    /// Image format of the file referenced by the blob.
188    pub format: ImageFormat,
189}
190
191impl ImageBlob {
192    pub(crate) fn from_rep_node(rep_node: &Node) -> Result<Self> {
193        if let Some(node) = &rep_node.children().find(|n| n.has_tag_name("jpegImage")) {
194            Ok(Self {
195                data: Blob::from_node(node)?,
196                format: ImageFormat::Jpeg,
197            })
198        } else if let Some(node) = &rep_node.children().find(|n| n.has_tag_name("pngImage")) {
199            Ok(Self {
200                data: Blob::from_node(node)?,
201                format: ImageFormat::Png,
202            })
203        } else {
204            Error::invalid("Cannot find PNG or JPEG blob")
205        }
206    }
207
208    pub(crate) fn xml_string(&self) -> String {
209        match self.format {
210            ImageFormat::Png => self.data.xml_string("pngImage"),
211            ImageFormat::Jpeg => self.data.xml_string("jpegImage"),
212        }
213    }
214}
215
216/// Properties of an visual reference image.
217#[derive(Clone, Debug)]
218pub struct VisualReferenceImageProperties {
219    /// Width of the image in pixels.
220    pub width: u32,
221    /// Height of the image in pixels.
222    pub height: u32,
223}
224
225/// A visual reference image for preview and illustration purposes.
226///
227/// Such images cannot be mapped to points and are not projectable!
228#[derive(Clone, Debug)]
229#[non_exhaustive]
230pub struct VisualReferenceImage {
231    /// Reference to the binary image data.
232    pub blob: ImageBlob,
233    /// Properties of the visual reference image.
234    pub properties: VisualReferenceImageProperties,
235    /// Reference to a PNG image with a mask for non-rectangular images.
236    ///
237    /// The mask is used to indicate which pixels in the image are valid.
238    /// The mask dimension are the same as the image itself.
239    /// It has non-zero-valued pixels at locations where the image is valid
240    /// and zero-valued pixels at locations where it is invalid.
241    pub mask: Option<Blob>,
242}
243
244impl VisualReferenceImage {
245    pub(crate) fn from_node(node: &Node) -> Result<Self> {
246        Ok(Self {
247            blob: ImageBlob::from_rep_node(node)?,
248            mask: Blob::from_parent_node("imageMask", node)?,
249            properties: VisualReferenceImageProperties {
250                width: xml::req_int(node, "imageWidth")?,
251                height: xml::req_int(node, "imageHeight")?,
252            },
253        })
254    }
255
256    pub(crate) fn xml_string(&self) -> String {
257        let mut xml = String::new();
258        xml += "<visualReferenceRepresentation type=\"Structure\">\n";
259        xml += &self.blob.xml_string();
260        if let Some(mask) = &self.mask {
261            xml += &mask.xml_string("imageMask");
262        }
263        xml += &xml::gen_int("imageWidth", self.properties.width);
264        xml += &xml::gen_int("imageHeight", self.properties.height);
265        xml += "</visualReferenceRepresentation>\n";
266        xml
267    }
268}
269
270/// Properties of a pinhole image.
271#[derive(Clone, Debug)]
272pub struct PinholeImageProperties {
273    /// Width of the image in pixels.
274    pub width: u32,
275    /// Height of the image in pixels.
276    pub height: u32,
277    /// The cameras focal length in meters.
278    pub focal_length: f64,
279    /// The width of a pixel in meters.
280    pub pixel_width: f64,
281    /// The height of a pixel in meters.
282    pub pixel_height: f64,
283    /// The X coordinate of the principal point in pixels.
284    pub principal_x: f64,
285    /// The Y coordinate of the principal point in pixels.
286    pub principal_y: f64,
287}
288
289/// Describes an image with a pinhole camera projection model.
290#[derive(Clone, Debug)]
291#[non_exhaustive]
292pub struct PinholeImage {
293    /// Reference to the binary image data.
294    pub blob: ImageBlob,
295    /// Properties of the pinhole image.
296    pub properties: PinholeImageProperties,
297    /// Reference to a PNG image with a mask for non-rectangular images.
298    ///
299    /// The mask is used to indicate which pixels in the image are valid.
300    /// The mask dimension are the same as the image itself.
301    /// It has non-zero-valued pixels at locations where the image is valid
302    /// and zero-valued pixels at locations where it is invalid.
303    pub mask: Option<Blob>,
304}
305
306impl PinholeImage {
307    pub(crate) fn from_node(node: &Node) -> Result<Self> {
308        Ok(Self {
309            blob: ImageBlob::from_rep_node(node)?,
310            mask: Blob::from_parent_node("imageMask", node)?,
311            properties: PinholeImageProperties {
312                width: xml::req_int(node, "imageWidth")?,
313                height: xml::req_int(node, "imageHeight")?,
314                focal_length: xml::req_f64(node, "focalLength")?,
315                pixel_width: xml::req_f64(node, "pixelWidth")?,
316                pixel_height: xml::req_f64(node, "pixelHeight")?,
317                principal_x: xml::req_f64(node, "principalPointX")?,
318                principal_y: xml::req_f64(node, "principalPointY")?,
319            },
320        })
321    }
322
323    pub(crate) fn xml_string(&self) -> String {
324        let mut xml = String::new();
325        xml += "<pinholeRepresentation type=\"Structure\">\n";
326        xml += &self.blob.xml_string();
327        if let Some(mask) = &self.mask {
328            xml += &mask.xml_string("imageMask");
329        }
330        xml += &xml::gen_int("imageWidth", self.properties.width);
331        xml += &xml::gen_int("imageHeight", self.properties.height);
332        xml += &xml::gen_float("focalLength", self.properties.focal_length);
333        xml += &xml::gen_float("pixelWidth", self.properties.pixel_width);
334        xml += &xml::gen_float("pixelHeight", self.properties.pixel_height);
335        xml += &xml::gen_float("principalPointX", self.properties.principal_x);
336        xml += &xml::gen_float("principalPointY", self.properties.principal_y);
337        xml += "</pinholeRepresentation>\n";
338        xml
339    }
340}
341
342/// Properties of a spherical image.
343#[derive(Clone, Debug)]
344pub struct SphericalImageProperties {
345    /// Width of the image in pixels.
346    pub width: u32,
347    /// Height of the image in pixels.
348    pub height: u32,
349    /// The width of a pixel in radians.
350    pub pixel_width: f64,
351    /// The height of a pixel in radians.
352    pub pixel_height: f64,
353}
354
355/// Describes an image with a spherical projection model.
356#[derive(Clone, Debug)]
357#[non_exhaustive]
358pub struct SphericalImage {
359    /// Reference to the binary image data.
360    pub blob: ImageBlob,
361    /// Properties of the spherical image.
362    pub properties: SphericalImageProperties,
363    /// Reference to a PNG image with a mask for non-rectangular images.
364    ///
365    /// The mask is used to indicate which pixels in the image are valid.
366    /// The mask dimension are the same as the image itself.
367    /// It has non-zero-valued pixels at locations where the image is valid
368    /// and zero-valued pixels at locations where it is invalid.
369    pub mask: Option<Blob>,
370}
371
372impl SphericalImage {
373    pub(crate) fn from_node(node: &Node) -> Result<Self> {
374        let width = xml::req_int(node, "imageWidth")?;
375        let height = xml::req_int(node, "imageHeight")?;
376        Ok(Self {
377            blob: ImageBlob::from_rep_node(node)?,
378            mask: Blob::from_parent_node("imageMask", node)?,
379            properties: SphericalImageProperties {
380                width,
381                height,
382                // The spec says they are required but some files seeem to omit them :(
383                // We try to calculate default values from the image size (by assuming its a full pano).
384                pixel_width: xml::opt_f64(node, "pixelWidth")?.unwrap_or((2.0 * PI) / width as f64),
385                pixel_height: xml::opt_f64(node, "pixelHeight")?.unwrap_or(PI / height as f64),
386            },
387        })
388    }
389
390    pub(crate) fn xml_string(&self) -> String {
391        let mut xml = String::new();
392        xml += "<sphericalRepresentation type=\"Structure\">\n";
393        xml += &self.blob.xml_string();
394        if let Some(mask) = &self.mask {
395            xml += &mask.xml_string("imageMask");
396        }
397        xml += &xml::gen_int("imageWidth", self.properties.width);
398        xml += &xml::gen_int("imageHeight", self.properties.height);
399        xml += &xml::gen_float("pixelWidth", self.properties.pixel_width);
400        xml += &xml::gen_float("pixelHeight", self.properties.pixel_height);
401        xml += "</sphericalRepresentation>\n";
402        xml
403    }
404}
405
406/// Properties of a cylindrical image.
407#[derive(Clone, Debug)]
408pub struct CylindricalImageProperties {
409    /// Width of the image in pixels.
410    pub width: u32,
411    /// Height of the image in pixels.
412    pub height: u32,
413    /// The closest distance from the cylindrical image surface to the center of projection in meters.
414    pub radius: f64,
415    /// The Y coordinate of the principal point in pixels.
416    pub principal_y: f64,
417    /// The width of a pixel in radians.
418    pub pixel_width: f64,
419    /// The height of a pixel in radians.
420    pub pixel_height: f64,
421}
422
423/// Describes an image with a cylindrical projection model.
424#[derive(Clone, Debug)]
425#[non_exhaustive]
426pub struct CylindricalImage {
427    /// Reference to the binary image data.
428    pub blob: ImageBlob,
429    /// Properties of the yylindrical image.
430    pub properties: CylindricalImageProperties,
431    /// Reference to a PNG image with a mask for non-rectangular images.
432    ///
433    /// The mask is used to indicate which pixels in the image are valid.
434    /// The mask dimension are the same as the image itself.
435    /// It has non-zero-valued pixels at locations where the image is valid
436    /// and zero-valued pixels at locations where it is invalid.
437    pub mask: Option<Blob>,
438}
439
440impl CylindricalImage {
441    pub(crate) fn from_node(node: &Node) -> Result<Self> {
442        Ok(Self {
443            blob: ImageBlob::from_rep_node(node)?,
444            mask: Blob::from_parent_node("imageMask", node)?,
445            properties: CylindricalImageProperties {
446                width: xml::req_int(node, "imageWidth")?,
447                height: xml::req_int(node, "imageHeight")?,
448                radius: xml::req_f64(node, "radius")?,
449                principal_y: xml::req_f64(node, "principalPointY")?,
450                pixel_width: xml::req_f64(node, "pixelWidth")?,
451                pixel_height: xml::req_f64(node, "pixelHeight")?,
452            },
453        })
454    }
455
456    pub(crate) fn xml_string(&self) -> String {
457        let mut xml = String::new();
458        xml += "<cylindricalRepresentation type=\"Structure\">\n";
459        xml += &self.blob.xml_string();
460        if let Some(mask) = &self.mask {
461            xml += &mask.xml_string("imageMask");
462        }
463        xml += &xml::gen_int("imageWidth", self.properties.width);
464        xml += &xml::gen_int("imageHeight", self.properties.height);
465        xml += &xml::gen_float("readius", self.properties.radius);
466        xml += &xml::gen_float("principalPointY", self.properties.principal_y);
467        xml += &xml::gen_float("pixelWidth", self.properties.pixel_width);
468        xml += &xml::gen_float("pixelHeight", self.properties.pixel_height);
469        xml += "</cylindricalRepresentation>\n";
470        xml
471    }
472}