igd-geo / pasture

Rust library for point cloud processing
Apache License 2.0
72 stars 9 forks source link

pasture_io - reading wrong point coordinates with negative values #8

Closed tobias93 closed 2 years ago

tobias93 commented 3 years ago

There seems to be a bug in pasture_io, when reading las files with negative point coordinates.

I used a simple test file for this, that I created using the las crate with the following code. I checked the resulting file with cloudcompare and it seems to be correct:

use las::{Writer, Write, Point, Header, Builder, Vector, Transform};

let filename = "points.las";

// write las/laz file
{
    let mut writer = Writer::from_path(filename, Default::default()).unwrap();
    writer.write(Point { x: -2.0, y: -2.0, z: -2.0, .. Default::default() }).unwrap();
    writer.write(Point { x: -1.5, y: -1.5, z: -1.5, .. Default::default() }).unwrap();
    writer.write(Point { x: -1.0, y: -1.0, z: -1.0, .. Default::default() }).unwrap();
    writer.write(Point { x: -0.5, y: -0.5, z: -0.5, .. Default::default() }).unwrap();
    writer.write(Point { x: 0.0, y: 0.0, z: 0.0, .. Default::default() }).unwrap();
    writer.write(Point { x: 0.5, y: 0.5, z: 0.5, .. Default::default() }).unwrap();
    writer.write(Point { x: 1.0, y: 1.0, z: 1.0, .. Default::default() }).unwrap();
    writer.write(Point { x: 1.5, y: 1.5, z: 1.5, .. Default::default() }).unwrap();
    writer.write(Point { x: 2.0, y: 2.0, z: 2.0, .. Default::default() }).unwrap();
}

When reading the file back using pasture_io, I would expect it to read the same values that were written above. However, for negative values, the coordinate are wrong:

use pasture_io::base::IOFactory;
use pasture_core::containers::{PointBufferExt, PointBuffer};
use std::path::PathBuf;
use pasture_io::las::LasPointFormat0;
use las::point::Format;

let filename = "points.las";

// read with pasture io
{
    let mut pasture_reader = IOFactory::default().make_reader(&PathBuf::from(filename)).unwrap();
    let nr_points  = pasture_reader.get_metadata().number_of_points().unwrap();
    let points = pasture_reader.read(nr_points).unwrap();
    println!("nr points: {}", nr_points);
    for point in points.iter_point::<LasPointFormat0>() {
        println!("{:?}", point.position);
    }
}

nr points: 9 Matrix { data: [4294965.296, 4294965.296, 4294965.296] } Matrix { data: [4294965.796, 4294965.796, 4294965.796] } Matrix { data: [4294966.296, 4294966.296, 4294966.296] } Matrix { data: [4294966.796, 4294966.796, 4294966.796] } Matrix { data: [0.0, 0.0, 0.0] } Matrix { data: [0.5, 0.5, 0.5] } Matrix { data: [1.0, 1.0, 1.0] } Matrix { data: [1.5, 1.5, 1.5] } Matrix { data: [2.0, 2.0, 2.0] }

The negative numbers seem to be shifted by 4294967.296, which is 0.001 * 2^32. The factor of 0.001 seems to come from the transformation in the las header. I am not sure, where the 2^32 comes from, but it could be a hint for a signed integer, that is read as an unsigned one.

The problem only occurs when reading uncompressed .las files. The reader for .laz files does not have this problem.

Mortano commented 3 years ago

Thanks for the example. Pretty sure the culprit is this line here. Of course LAS stores coordinates as i32 and not u32. Should be easy to fix.

Mortano commented 3 years ago

This is fixed in main now and will be part of pasture 0.2.0 once it gets released.