Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Science data: lat/lon to cartesian #70

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5ec0460
Align lat long in science data visualization with spawned vehicle lat…
mabelzhang Nov 2, 2021
7a89db4
resolve conflicts with main
mabelzhang Nov 10, 2021
7ecf0e0
replace TODO TEMPORARY HACK comments with Performance trick
mabelzhang Nov 10, 2021
3dc76c7
cleanup
mabelzhang Nov 10, 2021
9cc1b42
Merge branch 'main' into mabelzhang/science_viz
mabelzhang Nov 10, 2021
3853681
Merge branch 'main' into mabelzhang/science_viz
mabelzhang Nov 10, 2021
2c8f6ae
Merge branch 'main' into mabelzhang/science_viz
mabelzhang Nov 13, 2021
3f6edfc
Merge from main; incorporate reading lat long instead of hard coding
mabelzhang Nov 13, 2021
98098c3
Merge branch 'main' into mabelzhang/science_viz
mabelzhang Nov 16, 2021
64eca32
add mutex for worldOriginSpherical*
mabelzhang Nov 16, 2021
dae526f
add BUILD_TESTING to README
mabelzhang Nov 16, 2021
17a165c
disable science sensors in empty_environment.sdf
mabelzhang Nov 16, 2021
6aa7076
manual cherry pick 643c97d from mabelzhang/interpolate_sci_raw_mat
mabelzhang Nov 16, 2021
58af8e8
remove WorldOriginSphericalService from WorldCommPlugin
mabelzhang Nov 17, 2021
b6b5245
add depth to ConvertLatLonToCart for convenience
mabelzhang Nov 17, 2021
6a5b426
Suggestions to #70 (#85)
chapulina Nov 19, 2021
d443425
Merge branch 'main' into mabelzhang/science_viz
chapulina Nov 19, 2021
00128b9
Merge branch 'main' into mabelzhang/science_viz
mabelzhang Nov 24, 2021
c287169
PR comments
mabelzhang Nov 24, 2021
f68ec87
Merge branch 'main' into mabelzhang/science_viz
mabelzhang Nov 24, 2021
7bc8e98
merged from main
chapulina Nov 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 137 additions & 4 deletions lrauv_ignition_plugins/src/ScienceSensorsSystem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ using namespace tethys;

class tethys::ScienceSensorsSystemPrivate
{
/// \brief Initialize world origin in spherical coordinates
public: void UpdateWorldSphericalOrigin(
ignition::gazebo::EntityComponentManager &_ecm);

/// \brief Convert (lat, lon, 0) to Cartesian XYZ, including shifting by
/// world origin
public: void ConvertLatLonToCart(
const ignition::math::Vector3d &_latLonEle,
ignition::math::Vector3d &_cart);

/// \brief Shift point cloud with respect to the world origin in spherical
/// coordinates, if available.
public: void ShiftDataToNewSphericalOrigin();

/// \brief Reads csv file and populate various data fields
public: void ReadData();

Expand Down Expand Up @@ -107,9 +121,29 @@ class tethys::ScienceSensorsSystemPrivate
/// \brief Indicates whether data has been loaded
public: bool initialized {false};

/// \brief Set to true after the spherical coordinates have been initialized.
/// This may happen at startup if the SDF file has them hardcoded, or at
/// runtime when the first vehicle is spawned. Assume the coordinates are
/// only shifted once.
public: bool worldSphericalCoordsInitialized {false};

/// \brief Mutex for writing to world origin association to lat/long
public: std::mutex mtx;

/// \brief Spherical coordinates of world origin. Can change at any time.
public: ignition::math::SphericalCoordinates worldOriginSphericalCoords;

/// \brief World origin in spherical position (latitude, longitude,
/// elevation), angles in degrees
public: ignition::math::Vector3d worldOriginSphericalPos = {0, 0, 0};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to store this variable? I don't see it being used anywhere besides right after it is set.

Copy link
Collaborator Author

@mabelzhang mabelzhang Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I print this out in ReadData() in #83. That might be another forward-merge thing. I think this is a useful variable that I'll be using for debugging until all the sensor stuff is finalized. We're still pretty far from that, at least 2 more PRs, before actually testing the missions, which will probably be 1 more PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's just holding duplicate data that's already in worldOriginSphericalCoords, right? Couldn't you just print the values from that? Keeping the same information in 2 places is prone to error because we need to keep them in sync.


/// \brief World origin in Cartesian coordinates converted from spherical
/// coordinates
public: ignition::math::Vector3d worldOriginCartesianCoords = {0, 0, 0};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this variable. Isn't the world origin in Cartesian always zero? What does a non-zero cartesian origin mean?


/// \brief For conversions
public: ignition::math::SphericalCoordinates sphCoord;

/// \brief Whether using more than one time slices of data
public: bool multipleTimeSlices {false};

Expand Down Expand Up @@ -190,9 +224,9 @@ class tethys::ScienceSensorsSystemPrivate
// For 2003080103_mb_l3_las_1x1km.csv
//public: const float MINIATURE_SCALE = 0.01;
// For 2003080103_mb_l3_las.csv
//public: const float MINIATURE_SCALE = 0.0001;
public: const float MINIATURE_SCALE = 0.0001;
// For simple_test.csv
public: const float MINIATURE_SCALE = 1000.0;
// public: const float MINIATURE_SCALE = 1000.0;

// TODO This is a workaround pending upstream Marker performance improvements.
// \brief Performance trick. Skip depths below this z, so have memory to
Expand Down Expand Up @@ -437,8 +471,9 @@ void ScienceSensorsSystemPrivate::ReadData()
continue;
}

// TODO: Convert spherical coordinates to Cartesian
ignition::math::Vector3d cart = {latitude, longitude, -depth};
// Convert spherical coordinates to Cartesian
ignition::math::Vector3d cart;
this->ConvertLatLonToCart({latitude, longitude, -depth}, cart);

// Performance trick. Scale down to see in view
cart *= this->MINIATURE_SCALE;
Expand All @@ -453,6 +488,7 @@ void ScienceSensorsSystemPrivate::ReadData()

// Gather spatial coordinates, 3 fields in the line, into point cloud
// for indexing this time slice of data.
// Flip sign of z, because positive depth is negative z.
this->timeSpaceCoords[lineTimeIdx]->push_back(
pcl::PointXYZ(cart.X(), cart.Y(), cart.Z()));

Expand Down Expand Up @@ -487,6 +523,93 @@ void ScienceSensorsSystemPrivate::ReadData()
this->initialized = true;
}

/////////////////////////////////////////////////
void ScienceSensorsSystemPrivate::UpdateWorldSphericalOrigin(
ignition::gazebo::EntityComponentManager &_ecm)
{
// Lock for changes to worldOriginSpherical* until update complete
std::lock_guard<std::mutex> lock(mtx);

// Data positions have not been transformed wrt world origin lat/long
if (!this->worldSphericalCoordsInitialized)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: In situations like this, it looks a bit cleaner if you return early on the opposite condition. It's easier to read the code if we know that the function ends here, and also reduces indentation of the block below.

Suggested change
if (!this->worldSphericalCoordsInitialized)
if (this->worldSphericalCoordsInitialized)
return;

I think we can lock the mutex after this block?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we cannot return right away. That's actually a logic error due to incorrect braces. Thanks for catching that.

The if-statement is there because UpdateWorldSphericalOrigin() is called from two places, one in Configure() (because otherwise no data is shown), and the other in PreUpdate(). In the former case, the variable worldSphericalCoordsInitialized is false, and in the latter case, it is true. BUT, in both cases, ShiftDataToNewSphericalOrigin() needs to be called. So the ShiftDataToNewSphericalOrigin() call is supposed to go outside the if-statement.

That hadn't been caught because ShiftDataToNewSphericalOrigin() is not coded yet! So both cases the behavior is the same.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pulled the function call out of the if-statement (to be committed, will post hash), but the logic might need to be changed once the function is coded. The logic is a combination of this->worldSphericalCoordsInitialized and this->initialized, which needs to be thought about and tested after ShiftDataToNewSphericalOrigin() is implemented.

Really, I just put the calls in there for whoever that implements ShiftDataToNewSphericalOrigin() to be aware of these different circumstances under which UpdateWorldSphericalOrigin() may be called. The call to UpdateWorldSphericalOrigin() in PreUpdate() needs to be tested at the same time too, because that condition has also never happened so far. It only happens if a vehicle is spawned.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODOs added in c287169

{
auto latLon = this->world.SphericalCoordinates(_ecm);
if (latLon)
{
ignwarn << "New spherical coordinates detected: "
<< latLon.value().LatitudeReference().Degree() << ", "
<< latLon.value().LongitudeReference().Degree() << std::endl;

this->worldOriginSphericalCoords = latLon.value();

// Extract world origin in (lat, long, elevation) from spherical coords
this->worldOriginSphericalPos = ignition::math::Vector3d(
this->worldOriginSphericalCoords.LatitudeReference().Degree(),
this->worldOriginSphericalCoords.LongitudeReference().Degree(),
// TODO look into why converting with elevation don't give depth as z
// Details https://github.com/osrf/lrauv/pull/70#discussion_r755679895
//this->worldOriginSphericalCoords.ElevationReference());
0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to hardcode 0 here?

Suggested change
0);
this->worldOriginSphericalCoords.ElevationReference());

Copy link
Collaborator Author

@mabelzhang mabelzhang Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's something weird about the conversion. I don't know if it's because I'm using the function wrong, or we're not using Z the way the function assumes.

The depth conversion result looks wrong if I give it the actual elevation, in that it doesn't give us -depth as z. Since we're treating z = 0 as surface, and z is supposed to be -depth, I just do the lat/long transformation with elevation 0, and then put in the z manually. That is done in ConvertLatLonToCart() (it's a function because I use it more than once in newer code in #83, some of which I backported to here) as well as here.

Short of a fix, I can add a TODO linking to this comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO added in c287169

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I reproduce the failure? I see buoyant_tethys.sdf is currently setting the <elevation> to zero, so this change here shouldn't make a difference.

// Convert spherical coordinates to Cartesian
this->sphCoord = ignition::math::SphericalCoordinates(
this->worldOriginSphericalCoords.Surface());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me what's the intended difference between sphCoord and worldOriginSphericalCoords. What this is doing is:

  1. Convert worldOriginSphericalCoords to worldOriginSphericalPos
  2. Convert worldOriginSphericalPos to sphCoord

Are they supposed to be different?

Copy link
Collaborator Author

@mabelzhang mabelzhang Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They gave me different results. I don't know why, but I had to create a new one for the markers to be at reasonable places. The difference is that worldOriginSphericalCoords contains everything about the world origin, and sphCoord is a blank object that only contains Surface(), which is probably wrong, but it gives results at the origin, which there should be, since the origin is set to be at a lat/long copied straight from the CSV.

I tried it again with this fix in both places in the code, mentioned here #70 (comment) , and the former is still giving positions like this:

[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 0 at -440.367, 365.239, -0, value 0.922332, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 1 at -439.904, 366.508, -0, value 0.437973, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 2 at -441.047, 366.87, -0, value 0.104822, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 3 at -442.178, 367.232, -0, value 0.0355906, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 4 at -438.288, 367.413, -0, value 0.268712, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 5 at -443.296, 367.594, -0, value 0.426041, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 6 at -439.437, 367.775, -0, value 0.527025, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 7 at -440.574, 368.136, -0, value 0.207158, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 8 at -441.7, 368.497, -0, value 0.954847, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 9 at -437.822, 368.678, -0, value 0.0730943, dimX 0.38088

sphCoords gives points near the origin, which I set in the SDF to an exact lat/long that has a data point, so this looks more correct:

[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 0 at 0.989183, -3.98017, -0, value 0.922332, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 1 at 1.45222, -2.71143, -0, value 0.437973, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 2 at 0.309398, -2.34932, -0, value 0.104822, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 3 at -0.821341, -1.98739, -0, value 0.0355906, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 4 at 3.06878, -1.80649, -0, value 0.268712, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 5 at -1.94, -1.62567, -0, value 0.426041, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 6 at 1.91938, -1.44486, -0, value 0.527025, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 7 at 0.782051, -1.08337, -0, value 0.207158, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 8 at -0.343238, -0.722056, -0, value 0.954847, dimX 0.38088
[GUI] [Dbg] [VisualizePointCloud.cc:477] Added point 9 at 3.53456, -0.541467, -0, value 0.0730943, dimX 0.38088

I will say at the time this was coded, I just needed something, anything, to show up at the origin, so a quick hack was needed. So while sphCoord looked wrong to me, I didn't have time to fix it.

Here's what I would propose. Right now, since #83 is pending on this, I would propose to delay the fix again, so that #83 isn't blocked. I know that's not good, but I also suspect after #83 and another PR to do the interpolation, then we can actually test the sensor locations and maybe figure out why this is wrong.

Very honestly, right now, there is still realistically nothing to test this code. We see some colors, but we really have no idea whether they're correct wrt neither lat/long nor Cartesian! So all of these PRs are just code that won't be tested until a few weeks later (but hopefully much much sooner, like before end of month fingers crossed), when we run the MBARI acceptance mission.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO added in c287169

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose to delay the fix again, so that #83 isn't blocked

I'm ok fixing this incrementally, my only concern is that the positions could be completely wrong right now. Would that affect how #83 is implemented?

right now, there is still realistically nothing to test this code

I think we can come up with some tests, we just need to create some dummy data that we know exactly where it should go. In fact, I think we should be working against this kind of data before we try to load the real datasets, because it's much easier to create different scenarios to test.

My suggestion would be to add more datapoints to dummy.csv and check that they behave as expected. I'll do that now locally here to help me wrap my head around this PR.

this->worldOriginCartesianCoords =
// TODO look into why converting with worldOriginSphericalCoords gives
// unexpected positions.
// Details https://github.com/osrf/lrauv/pull/70#discussion_r755681564
//this->worldOriginSphericalCoords.LocalFromSphericalPosition(
this->sphCoord.LocalFromSphericalPosition(
this->worldOriginSphericalPos);

igndbg << "Data will be transformed wrt world origin in Cartesian ("
<< this->worldOriginCartesianCoords.X() << ", "
<< this->worldOriginCartesianCoords.Y() << ", "
<< this->worldOriginCartesianCoords.Z() << ")" << std::endl;

this->worldSphericalCoordsInitialized = true;
}
}

// TODO(chapulina) Shift science data to new coordinates. This logic might
// need to be updated.
// If data had been loaded before origin was updated, need to shift data
if (this->initialized)
{
this->ShiftDataToNewSphericalOrigin();
}
}

/////////////////////////////////////////////////
void ScienceSensorsSystemPrivate::ConvertLatLonToCart(
const ignition::math::Vector3d &_latLonEle,
ignition::math::Vector3d &_cart)
{
// Convert spherical coordinates to Cartesian
_cart = this->sphCoord.LocalFromSphericalPosition(
ignition::math::Vector3d(_latLonEle.X(), _latLonEle.Y(), 0));

// Shift to be relative to world origin spherical coordinates
_cart -= this->worldOriginCartesianCoords;

// Set depth
_cart.Z() = _latLonEle.Z();

/*
igndbg << "Data point at Cartesian ("
<< _cart.X() << ", "
<< _cart.Y() << ", "
<< _cart.Z() << ")" << std::endl;
*/
}

/////////////////////////////////////////////////
void ScienceSensorsSystemPrivate::ShiftDataToNewSphericalOrigin()
{
// Lock modifications to world origin spherical association until finish
// transforming data
std::lock_guard<std::mutex> lock(mtx);
}

/////////////////////////////////////////////////
void ScienceSensorsSystemPrivate::GenerateOctrees()
{
Expand Down Expand Up @@ -602,6 +725,10 @@ void ScienceSensorsSystem::Configure(
this->dataPtr->salPub = this->dataPtr->node.Advertise<
ignition::msgs::Float_V>("/salinity");

// Initialize world origin in spherical coordinates, so data is loaded to the
// correct Cartesian positions.
this->dataPtr->UpdateWorldSphericalOrigin(_ecm);

this->dataPtr->ReadData();
this->dataPtr->GenerateOctrees();

Expand All @@ -614,6 +741,12 @@ void ScienceSensorsSystem::PreUpdate(
const ignition::gazebo::UpdateInfo &,
ignition::gazebo::EntityComponentManager &_ecm)
{
// TODO: Test this logic once ShiftDataToNewSphericalOrigin() is implemented
if (!this->dataPtr->worldSphericalCoordsInitialized)
{
this->dataPtr->UpdateWorldSphericalOrigin(_ecm);
}

_ecm.EachNew<ignition::gazebo::components::CustomSensor,
ignition::gazebo::components::ParentEntity>(
[&](const ignition::gazebo::Entity &_entity,
Expand Down
26 changes: 13 additions & 13 deletions lrauv_ignition_plugins/src/VisualizePointCloud.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ namespace tethys

/// \brief Performance trick. Render only every other n. Increase to render
/// fewer markers (faster performance). Recalulated in function.
public: int renderEvery = 1;
public: int renderEvery = 20;

/// \brief Performance trick. Skip depths below this z, so have memory to
/// visualize higher layers at higher resolution.
Expand All @@ -95,40 +95,42 @@ namespace tethys
// For 2003080103_mb_l3_las_1x1km.csv
//public: const float MINIATURE_SCALE = 0.01;
// For 2003080103_mb_l3_las.csv
//public: const float MINIATURE_SCALE = 0.0001;
public: const float MINIATURE_SCALE = 0.0001;
// For simple_test.csv
public: const float MINIATURE_SCALE = 1000.0;
// public: const float MINIATURE_SCALE = 1000.0;

/// \brief Performance trick. Factor to multiply in calculating Marker sizes
// For 2003080103_mb_l3_las*.csv
//public: float dimFactor = 0.03;
public: float dimFactor = 0.03;
// For simple_test.csv
public: float dimFactor = 0.003;
// public: float dimFactor = 0.003;

/// \brief Parameter to calculate Marker size in x.
/// Performance trick. Hardcode resolution to make markers resemble voxels.
// For 2003080103_mb_l3_las_1x1km.csv
//public: const float RES_X = 15 * MINIATURE_SCALE;
//public: const float RES_Y = 22 * MINIATURE_SCALE;
//public: const float RES_Z = 5 * MINIATURE_SCALE;
// For 2003080103_mb_l3_las.csv
//public: const float RES_X = 15;
public: const float RES_X = 15;
// For simple_test.csv
public: const float RES_X = 0.15;
// public: const float RES_X = 0.15;

/// \brief Parameter to calculate Marker size in y.
// For 2003080103_mb_l3_las_1x1km.csv
//public: const float RES_Y = 22 * MINIATURE_SCALE;
// For 2003080103_mb_l3_las.csv
//public: const float RES_Y = 22;
public: const float RES_Y = 22;
// For simple_test.csv
public: const float RES_Y = 0.22;
// public: const float RES_Y = 0.22;

/// \brief Parameter to calculate Marker size in z.
// For 2003080103_mb_l3_las_1x1km.csv
//public: const float RES_Z = 5 * MINIATURE_SCALE;
// For 2003080103_mb_l3_las.csv
//public: const float RES_Z = 10;
public: const float RES_Z = 10;
// For simple_test.csv
public: const float RES_Z = 0.10;
// public: const float RES_Z = 0.10;

/// \brief Minimum value in latest float vector
public: float minFloatV{std::numeric_limits<float>::max()};
Expand Down Expand Up @@ -380,8 +382,6 @@ void VisualizePointCloud::OnFloatVService(
//////////////////////////////////////////////////
void VisualizePointCloud::PublishMarkers()
{
IGN_PROFILE("VisualizePointCloud::PublishMarkers");

if (!this->dataPtr->showing)
return;

Expand Down
8 changes: 4 additions & 4 deletions lrauv_ignition_plugins/worlds/buoyant_tethys.sdf
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,18 @@
<longitude_deg>-121.829647676843</longitude_deg-->

<!-- For simple_test.csv -->
<latitude_deg>0</latitude_deg>
<longitude_deg>0</longitude_deg>
<!--latitude_deg>0</latitude_deg>
<longitude_deg>0</longitude_deg-->

<elevation>0</elevation>
<heading_deg>0</heading_deg>
</spherical_coordinates>
<plugin
filename="ScienceSensorsSystem"
name="tethys::ScienceSensorsSystem">
<!--data_path>2003080103_mb_l3_las.csv</data_path-->
<data_path>2003080103_mb_l3_las.csv</data_path>
<!--data_path>2003080103_mb_l3_las_1x1km.csv</data_path-->
<data_path>simple_test.csv</data_path>
<!--data_path>simple_test.csv</data_path-->
</plugin>

<gui fullscreen="0">
Expand Down