Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
63 changes: 52 additions & 11 deletions apps/mm2las/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <mp2p_icp/metricmap.h>
#include <mrpt/3rdparty/tclap/CmdLine.h>
#include <mrpt/maps/CPointsMap.h>
#include <mrpt/poses/CPose3D.h>
#include <mrpt/system/filesystem.h>
#include <mrpt/system/string_utils.h>
#include <mrpt/version.h>
Expand Down Expand Up @@ -55,6 +56,12 @@ TCLAP::ValueArg<std::string> argGeneratingSoftware(
"", "generating-software", "Generating Software for LAS header (max 32 chars)", false,
"MOLA mm2las", "string", cmd);

TCLAP::ValueArg<std::string> argFrame(
"", "frame",
"Coordinate frame for exported points: 'map' (default) uses the map local frame; "
"'enu' transforms points to the East-North-Up frame (requires georeferencing data in the map).",
false, "map", "map|enu", cmd);

// ----------------------------------------------------------------
// LAS 1.4 Format Structures
// ----------------------------------------------------------------
Expand Down Expand Up @@ -171,7 +178,8 @@ struct FieldInfo
void saveToLas(
const mrpt::maps::CPointsMap& pc, const std::string& filename,
const std::vector<std::string>& selectedFields, const std::string& systemId,
const std::string& generatingSoftware)
const std::string& generatingSoftware,
const std::optional<mrpt::poses::CPose3D>& T_enu_to_map = std::nullopt)
{
const size_t N = pc.size();
if (N == 0)
Expand Down Expand Up @@ -590,12 +598,17 @@ void saveToLas(

for (size_t i = 0; i < N; ++i)
{
min_x = std::min(min_x, static_cast<double>(xs[i]));
max_x = std::max(max_x, static_cast<double>(xs[i]));
min_y = std::min(min_y, static_cast<double>(ys[i]));
max_y = std::max(max_y, static_cast<double>(ys[i]));
min_z = std::min(min_z, static_cast<double>(zs[i]));
max_z = std::max(max_z, static_cast<double>(zs[i]));
double px = xs[i], py = ys[i], pz = zs[i];
if (T_enu_to_map.has_value())
{
T_enu_to_map->composePoint(xs[i], ys[i], zs[i], px, py, pz);
}
min_x = std::min(min_x, px);
max_x = std::max(max_x, px);
min_y = std::min(min_y, py);
max_y = std::max(max_y, py);
min_z = std::min(min_z, pz);
max_z = std::max(max_z, pz);
Comment thread
jlblancoc marked this conversation as resolved.
}

// 5. Calculate extra bytes for extra dimensions
Expand Down Expand Up @@ -746,10 +759,17 @@ void saveToLas(
{
LASPointFormat8 pt = {};

// On-the-fly coordinate transform if exporting in ENU frame
double px = xs[i], py = ys[i], pz = zs[i];
if (T_enu_to_map.has_value())
{
T_enu_to_map->composePoint(xs[i], ys[i], zs[i], px, py, pz);
}

// Scale coordinates
pt.x = static_cast<int32_t>((xs[i] - header.x_offset) / header.x_scale_factor);
pt.y = static_cast<int32_t>((ys[i] - header.y_offset) / header.y_scale_factor);
pt.z = static_cast<int32_t>((zs[i] - header.z_offset) / header.z_scale_factor);
pt.x = static_cast<int32_t>((px - header.x_offset) / header.x_scale_factor);
pt.y = static_cast<int32_t>((py - header.y_offset) / header.y_scale_factor);
pt.z = static_cast<int32_t>((pz - header.z_offset) / header.z_scale_factor);

// Intensity
if (hasIntensity)
Expand Down Expand Up @@ -930,6 +950,27 @@ int main(int argc, char** argv)
mp2p_icp::metric_map_t mm;
mm.load_from_file(arg_input.getValue());

// Handle --frame option
std::optional<mrpt::poses::CPose3D> T_enu_to_map;
{
const auto frame = argFrame.getValue();
if (frame == "enu")
{
if (!mm.georeferencing.has_value())
{
throw std::runtime_error(
"Cannot use --frame enu: the input map does not contain georeferencing "
"information.");
}
T_enu_to_map = mm.georeferencing->T_enu_to_map.mean;
std::cout << "[mm2las] Exporting points in ENU frame." << std::endl;
}
else if (frame != "map")
{
throw std::runtime_error("Invalid --frame value. Must be 'map' or 'enu'.");
}
}

std::string prefix = arg_out_prefix.getValue().empty()
? mrpt::system::fileNameChangeExtension(arg_input.getValue(), "")
: arg_out_prefix.getValue();
Expand Down Expand Up @@ -988,7 +1029,7 @@ int main(int argc, char** argv)
std::string out = prefix + "_" + name + ".las";
std::cout << "Exporting '" << name << "' to " << out << " (" << pts->size()
<< " points)..." << std::endl;
saveToLas(*pts, out, selectedFields, systemId, generatingSoftware);
saveToLas(*pts, out, selectedFields, systemId, generatingSoftware, T_enu_to_map);
}

std::cout << "Done." << std::endl;
Expand Down
51 changes: 49 additions & 2 deletions apps/mm2ply/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <mp2p_icp/metricmap.h>
#include <mrpt/3rdparty/tclap/CmdLine.h>
#include <mrpt/maps/CPointsMap.h>
#include <mrpt/poses/CPose3D.h>
#include <mrpt/system/filesystem.h>
#include <mrpt/system/string_utils.h>
#include <mrpt/version.h>
Expand Down Expand Up @@ -52,12 +53,19 @@ TCLAP::SwitchArg argIgnoreMissingFields(
"instead of an error; missing fields are skipped.",
cmd);

TCLAP::ValueArg<std::string> argFrame(
"", "frame",
"Coordinate frame for exported points: 'map' (default) uses the map local frame; "
"'enu' transforms points to the East-North-Up frame (requires georeferencing data in the map).",
false, "map", "map|enu", cmd);

// ----------------------------------------------------------------
// PLY Export Logic using CPointsMap field-generic API
// ----------------------------------------------------------------
void saveToPly(
const mrpt::maps::CPointsMap& pc, const std::string& filename, bool binary,
const std::vector<std::string>& selectedFields = {}, bool ignoreMissingFields = false)
const std::vector<std::string>& selectedFields = {}, bool ignoreMissingFields = false,
const std::optional<mrpt::poses::CPose3D>& T_enu_to_map = std::nullopt)
{
std::ofstream f;
f.open(filename, binary ? std::ios::binary : std::ios::out);
Expand Down Expand Up @@ -296,6 +304,13 @@ void saveToPly(

for (size_t i = 0; i < N; ++i)
{
// On-the-fly coordinate transform if exporting in ENU frame
double tx = 0, ty = 0, tz = 0;
if (T_enu_to_map.has_value())
{
T_enu_to_map->composePoint(xs[i], ys[i], zs[i], tx, ty, tz);
}
Comment thread
jlblancoc marked this conversation as resolved.

for (const auto& acc : accessors)
{
// Handle missing fields (when ignoreMissingFields is true)
Expand Down Expand Up @@ -325,6 +340,15 @@ void saveToPly(
continue;
}

// Use transformed coordinates if in ENU mode
if (T_enu_to_map.has_value() && (acc.name == "x" || acc.name == "y" || acc.name == "z"))
{
const float val =
static_cast<float>(acc.name == "x" ? tx : (acc.name == "y" ? ty : tz));
write_val(val, "%.8e");
continue;
}

switch (acc.type)
{
default:
Expand Down Expand Up @@ -380,6 +404,27 @@ int main(int argc, char** argv)
mp2p_icp::metric_map_t mm;
mm.load_from_file(arg_input.getValue());

// Handle --frame option
std::optional<mrpt::poses::CPose3D> T_enu_to_map;
{
const auto frame = argFrame.getValue();
if (frame == "enu")
{
if (!mm.georeferencing.has_value())
{
throw std::runtime_error(
"Cannot use --frame enu: the input map does not contain georeferencing "
"information.");
}
T_enu_to_map = mm.georeferencing->T_enu_to_map.mean;
std::cout << "[mm2ply] Exporting points in ENU frame." << std::endl;
}
else if (frame != "map")
{
throw std::runtime_error("Invalid --frame value. Must be 'map' or 'enu'.");
}
}

std::string prefix = arg_out_prefix.getValue().empty()
? mrpt::system::fileNameChangeExtension(arg_input.getValue(), "")
: arg_out_prefix.getValue();
Expand Down Expand Up @@ -534,7 +579,9 @@ int main(int argc, char** argv)

std::string out = prefix + "_" + name + ".ply";
std::cout << "Exporting '" << name << "' to " << out << "..." << std::endl;
saveToPly(*pts, out, arg_binary.getValue(), validFields);
saveToPly(
*pts, out, arg_binary.getValue(), validFields, false /*ignoreMissing*/,
T_enu_to_map);
}
}
catch (const std::exception& e)
Expand Down
44 changes: 39 additions & 5 deletions apps/mm2txt/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <mrpt/containers/yaml.h>
#include <mrpt/core/Clock.h>
#include <mrpt/maps/CGenericPointsMap.h>
#include <mrpt/poses/CPose3D.h>
#include <mrpt/system/filesystem.h>
#include <mrpt/system/os.h>
#include <mrpt/system/string_utils.h>
Expand Down Expand Up @@ -61,9 +62,16 @@ TCLAP::SwitchArg argIgnoreMissingFields(
"instead of an error; missing fields are skipped.",
cmd);

TCLAP::ValueArg<std::string> argFrame(
"", "frame",
"Coordinate frame for exported points: 'map' (default) uses the map local frame; "
"'enu' transforms points to the East-North-Up frame (requires georeferencing data in the map).",
false, "map", "map|enu", cmd);

bool saveToTxt(
const mrpt::maps::CGenericPointsMap& pts, const std::string& fileName, bool printHeader,
const std::vector<std::string>& selectedFields = {})
const std::vector<std::string>& selectedFields = {},
const std::optional<mrpt::poses::CPose3D>& T_enu_to_map = std::nullopt)
{
FILE* f = mrpt::system::os::fopen(fileName.c_str(), "wt");
if (!f)
Expand Down Expand Up @@ -141,22 +149,29 @@ bool saveToTxt(

for (size_t i = 0; i < n; i++)
{
// On-the-fly coordinate transform if exporting in ENU frame
double tx = xs.at(i), ty = ys.at(i), tz = zs.at(i);
if (T_enu_to_map.has_value())
{
T_enu_to_map->composePoint(xs.at(i), ys.at(i), zs.at(i), tx, ty, tz);
}
Comment thread
jlblancoc marked this conversation as resolved.

for (size_t fieldIdx = 0; fieldIdx < fieldsToExport.size(); fieldIdx++)
{
const auto& fieldName = fieldsToExport[fieldIdx];

// Check coordinate fields
if (fieldName == "x")
{
mrpt::system::os::fprintf(f, "%.8f", xs.at(i));
mrpt::system::os::fprintf(f, "%.8f", tx);
}
else if (fieldName == "y")
{
mrpt::system::os::fprintf(f, "%.8f", ys.at(i));
mrpt::system::os::fprintf(f, "%.8f", ty);
}
else if (fieldName == "z")
{
mrpt::system::os::fprintf(f, "%.8f", zs.at(i));
mrpt::system::os::fprintf(f, "%.8f", tz);
}
// Check float fields
else if (floatFields.count(fieldName))
Expand Down Expand Up @@ -268,6 +283,25 @@ void run_mm2txt()
std::cout << std::endl;
}

// Handle --frame option
std::optional<mrpt::poses::CPose3D> T_enu_to_map;
{
const auto frame = argFrame.getValue();
if (frame == "enu")
{
ASSERTMSG_(
mm.georeferencing.has_value(),
"Cannot use --frame enu: the input map does not contain georeferencing "
"information.");
T_enu_to_map = mm.georeferencing->T_enu_to_map.mean;
std::cout << "[mm2txt] Exporting points in ENU frame.\n";
}
else
{
ASSERTMSG_(frame == "map", "Invalid --frame value. Must be 'map' or 'enu'.");
}
}

const auto printSelectedFieldsWarning = [&selectedFields]()
{
if (!selectedFields.empty())
Expand Down Expand Up @@ -368,7 +402,7 @@ void run_mm2txt()
}

bool printHeader = true;
saveToTxt(*genxyz, filName, printHeader, validFields);
saveToTxt(*genxyz, filName, printHeader, validFields, T_enu_to_map);
}
#if MRPT_VERSION < 0x030000 // <3.0.0
else
Expand Down
14 changes: 13 additions & 1 deletion docs/source/app_mm2las.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Usage

.. code-block:: bash

mm2las -i <input.mm> [-o <output_prefix>] [--export-fields <field1,field2,...>]
mm2las -i <input.mm> [-o <output_prefix>] [--export-fields <field1,field2,...>] [--frame map|enu]

Arguments
^^^^^^^^^
Expand All @@ -36,6 +36,7 @@ Arguments
- ``--export-fields <field1,field2,...>`` (optional): Comma-separated list of fields to export. If omitted, all available fields are exported, with non-standard fields becoming Extra Dimensions.
- ``--system-id <string>``: Sets the System Identifier in the LAS header (default: "mm2las").
- ``--generating-software <string>``: Sets the Generating Software in the LAS header (default: "MOLA mm2las").
- ``--frame <map|enu>`` (optional): Coordinate frame for exported points. ``map`` (default) exports coordinates in the map local frame. ``enu`` transforms all point coordinates to the East-North-Up frame using the georeferencing information stored in the map. Requires that the input map contains georeferencing data; otherwise, an error is raised.


Examples
Expand Down Expand Up @@ -65,6 +66,12 @@ Export with RGB colors:

mm2las -i mymap.mm --export-fields "x,y,z,red,green,blue"

Export points in the ENU (East-North-Up) frame:

.. code-block:: bash

mm2las -i mymap.mm --frame enu


Field Selection
---------------
Expand Down Expand Up @@ -123,6 +130,11 @@ Using PDAL:

LAZ files typically achieve 7-20× compression ratios while maintaining lossless data.

Coordinate Frames
-----------------

By default, points are exported in the **map** local frame (the native coordinate system of the metric map). If the map contains georeferencing metadata (see :ref:`app_mm-georef`), you can use ``--frame enu`` to export all point coordinates transformed to the **ENU (East-North-Up)** frame. The transformation uses the ``T_enu_to_map`` SE(3) pose stored in the map's georeferencing data. Non-coordinate fields (intensity, RGB, etc.) are not affected by this transformation.

Limitations
-----------

Expand Down
16 changes: 14 additions & 2 deletions docs/source/app_mm2ply.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Usage

.. code-block:: bash

mm2ply -i <input.mm> [-o <output_prefix>] [-b] [--export-fields <field1,field2,...>] [--ignore-missing-fields]
mm2ply -i <input.mm> [-o <output_prefix>] [-b] [--export-fields <field1,field2,...>] [--ignore-missing-fields] [--frame map|enu]

Arguments
^^^^^^^^^
Expand All @@ -34,6 +34,7 @@ Arguments
- ``-b, --binary`` (optional): Export in binary format instead of ASCII (default: ASCII)
- ``--export-fields <field1,field2,...>`` (optional): Comma-separated list of fields to export in the specified order. If not provided, all available fields will be exported. Spaces around commas are allowed
- ``--ignore-missing-fields`` (optional): If defined, the lack of any of the ``--export-fields`` in the map will be considered a warning instead of an error.
- ``--frame <map|enu>`` (optional): Coordinate frame for exported points. ``map`` (default) exports coordinates in the map local frame. ``enu`` transforms all point coordinates to the East-North-Up frame using the georeferencing information stored in the map. Requires that the input map contains georeferencing data; otherwise, an error is raised.

Examples
^^^^^^^^
Expand Down Expand Up @@ -80,6 +81,12 @@ Combine with custom output prefix:

mm2ply -i mymap.mm -o results/filtered --export-fields "x,y,z,ring,time"

Export points in the ENU (East-North-Up) frame:

.. code-block:: bash

mm2ply -i mymap.mm --frame enu

Output
------

Expand Down Expand Up @@ -130,4 +137,9 @@ The PLY format written by this tool follows the standard specification:
- **Header**: Describes vertex count and property types
- **Data**: One vertex per line (ASCII) or record (binary)

Color field name mapping ensures compatibility with standard PLY viewers and processing tools.
Color field name mapping ensures compatibility with standard PLY viewers and processing tools.

Coordinate Frames
-----------------

By default, points are exported in the **map** local frame (the native coordinate system of the metric map). If the map contains georeferencing metadata (see :ref:`app_mm-georef`), you can use ``--frame enu`` to export all point coordinates transformed to the **ENU (East-North-Up)** frame. The transformation uses the ``T_enu_to_map`` SE(3) pose stored in the map's georeferencing data. Non-coordinate fields (colors, intensity, etc.) are not affected by this transformation.
Loading