Skip to content

Commit 3a9016c

Browse files
committed
added ray based decimation, slow but perhaps useful
1 parent 7b56f26 commit 3a9016c

File tree

3 files changed

+120
-7
lines changed

3 files changed

+120
-7
lines changed

raycloudtools/raydecimate/raydecimate.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ void usage(int exit_code = 1)
2323
std::cout << "raydecimate raycloud 3 cm - reduces to one end point every 3 cm. A spatially even subsampling" << std::endl;
2424
std::cout << "raydecimate raycloud 4 rays - reduces to every fourth ray. A temporally even subsampling (if rays are chronological)" << std::endl;
2525
std::cout << "advanced methods not supported in rayrestore:" << std::endl;
26-
std::cout << "raydecimate raycloud 20 cm 64 rays - A maximum of 64 rays per cubic 20 cm. Retains small-scale details compared to spatial decimation" << std::endl;
27-
std::cout << "raydecimate raycloud 3 cm/m - reduces to ray ends spaced 3 cm apart for each metre of their length" << std::endl;
26+
std::cout << "raydecimate raycloud 20 cm 64 points - A maximum of 64 end points per cubic 20 cm. Retains small-scale details compared to spatial decimation" << std::endl;
27+
std::cout << "raydecimate raycloud 20 cm/ray - If all cells overlapping a ray intersect a ray then this way is not added" << std::endl;
28+
std::cout << "raydecimate raycloud 3 cm/m - reduces to ray ends spaced 3 cm apart for each metre of their length. Good for maintaining a range of point densities" << std::endl;
2829
// clang-format off
2930
exit(exit_code);
3031
}
@@ -49,20 +50,25 @@ int rayDecimate(int argc, char *argv[])
4950
{
5051
ray::FileArgument cloud_file;
5152
ray::IntArgument num_rays(1, 10000);
53+
ray::IntArgument width_for_ray(1, 10000);
5254
ray::DoubleArgument vox_width(0.01, 100.0);
5355
ray::DoubleArgument radius_per_length(0.01, 100.0);
54-
ray::ValueKeyChoice quantity({ &vox_width, &num_rays, &radius_per_length }, { "cm", "rays", "cm/m" });
55-
ray::TextArgument cm("cm"), rays("rays");
56+
ray::ValueKeyChoice quantity({ &vox_width, &num_rays, &radius_per_length, &width_for_ray }, { "cm", "rays", "cm/m", "cm/ray" });
57+
ray::TextArgument cm("cm"), points("points");
5658
bool standard_format = ray::parseCommandLine(argc, argv, { &cloud_file, &quantity });
57-
bool double_format = ray::parseCommandLine(argc, argv, { &cloud_file, &vox_width, &cm, &num_rays, &rays });
58-
if (!standard_format && !double_format)
59+
bool double_format_points = ray::parseCommandLine(argc, argv, { &cloud_file, &vox_width, &cm, &num_rays, &points });
60+
if (!standard_format && !double_format_points)
5961
usage();
6062

6163
bool res = false;
62-
if (double_format)
64+
if (double_format_points)
6365
{
6466
res = ray::decimateSpatioTemporal(cloud_file.nameStub(), vox_width.value(), num_rays.value());
6567
}
68+
else if (quantity.selectedKey() == "cm/ray")
69+
{
70+
res = ray::decimateRaysSpatial(cloud_file.nameStub(), width_for_ray.value());
71+
}
6672
else if (quantity.selectedKey() == "cm")
6773
{
6874
res = ray::decimateSpatial(cloud_file.nameStub(), vox_width.value());

raylib/raydecimation.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,107 @@ bool decimateSpatioTemporal(const std::string &file_stub, double vox_width, int
164164
return true;
165165
}
166166

167+
168+
bool decimateRaysSpatial(const std::string &file_stub, double vox_width)
169+
{
170+
ray::CloudWriter writer;
171+
if (!writer.begin(file_stub + "_decimated.ply"))
172+
return false;
173+
174+
// By maintaining these buffers below, we avoid almost all memory fragmentation
175+
ray::Cloud chunk;
176+
std::vector<int64_t> subsample;
177+
std::set<Eigen::Vector3i, ray::Vector3iLess> voxel_set;
178+
179+
auto decimate = [&](std::vector<Eigen::Vector3d> &starts, std::vector<Eigen::Vector3d> &ends,
180+
std::vector<double> &times, std::vector<ray::RGBA> &colours)
181+
{
182+
double width = 0.01 * vox_width;
183+
subsample.clear();
184+
for (int i = 0; i < (int)ends.size(); i++)
185+
{
186+
// #define END_FIRST // by traversing from end to start we match the existence of the ray more to the free space near the end point. In building1 test start first found more ray locations
187+
#if defined END_FIRST
188+
Eigen::Vector3d dir = starts[i] - ends[i];
189+
const Eigen::Vector3d source = ends[i] / width;
190+
const Eigen::Vector3d target = starts[i] / width;
191+
#else
192+
Eigen::Vector3d dir = ends[i] - starts[i];
193+
const Eigen::Vector3d source = starts[i] / width;
194+
const Eigen::Vector3d target = ends[i] / width;
195+
#endif
196+
const double length = dir.norm();
197+
for (int a = 0; a<3; a++)
198+
{
199+
if (dir[a] == 0.0)
200+
{
201+
dir[a] = 1e-10; // prevent division by 0
202+
}
203+
}
204+
const double eps = 1e-9; // to stay away from edge cases
205+
const double maxDist = (target - source).norm();
206+
207+
// cached values to speed up the loop below
208+
Eigen::Vector3i adds;
209+
Eigen::Vector3d offsets;
210+
for (int k = 0; k < 3; ++k)
211+
{
212+
if (dir[k] > 0.0)
213+
{
214+
adds[k] = 1;
215+
offsets[k] = 0.5;
216+
}
217+
else
218+
{
219+
adds[k] = -1;
220+
offsets[k] = -0.5;
221+
}
222+
}
223+
224+
Eigen::Vector3d p = source; // our moving variable as we walk over the grid
225+
Eigen::Vector3i inds((int)std::floor(p[0]), (int)std::floor(p[1]), (int)std::floor(p[2]));
226+
if (voxel_set.insert(inds).second)
227+
{
228+
subsample.push_back(i);
229+
continue;
230+
}
231+
double depth = 0;
232+
// for every ray, walk over its length and if there is a free space then add it
233+
do
234+
{
235+
double ls[3] = { (std::round(p[0] + offsets[0]) - p[0]) / dir[0], (std::round(p[1] + offsets[1]) - p[1]) / dir[1],
236+
(std::round(p[2] + offsets[2]) - p[2]) / dir[2] };
237+
int axis = (ls[0] < ls[1] && ls[0] < ls[2]) ? 0 : (ls[1] < ls[2] ? 1 : 2);
238+
inds[axis] += adds[axis];
239+
double minL = ls[axis] * length;
240+
depth += minL + eps;
241+
p = source + dir * (depth / length);
242+
243+
if (voxel_set.insert(inds).second)
244+
{
245+
subsample.push_back(i);
246+
break; // only adding to one cell
247+
}
248+
} while (depth <= maxDist);
249+
}
250+
chunk.resize(subsample.size());
251+
for (int64_t i = 0; i < (int64_t)subsample.size(); i++)
252+
{
253+
int64_t id = subsample[i];
254+
chunk.starts[i] = starts[id];
255+
chunk.ends[i] = ends[id];
256+
chunk.colours[i] = colours[id];
257+
chunk.times[i] = times[id];
258+
}
259+
writer.writeChunk(chunk);
260+
};
261+
262+
if (!ray::Cloud::read(file_stub + ".ply", decimate))
263+
return false;
264+
writer.end();
265+
return true;
266+
}
267+
167268
bool decimateAngular(const std::string &file_stub, double radius_per_length)
168269
{
169270
ray::CloudWriter writer;

raylib/raydecimation.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ bool RAYLIB_EXPORT decimateTemporal(const std::string &file_stub, int num_rays);
2727
/// This allows a more even distribution of points while maintaining details better than pure spatial decimation
2828
bool RAYLIB_EXPORT decimateSpatioTemporal(const std::string &file_stub, double vox_width, int num_rays);
2929

30+
/// @brief Maintains a maximum number of rays intersecting each voxel. This has some ambiguity, but is a useful routine
31+
/// as it maintains the integrity of the full ray cloud including free space, so is better for combine operations
32+
/// By contrast, standard spatial decimation removes free space whenever the end points coincide
33+
bool RAYLIB_EXPORT decimateRaysSpatial(const std::string &file_stub, double vox_width);
34+
35+
3036
/// @brief decimate to no more than 1 point per voxel of width @c radius_per_length x ray length.
3137
/// This is used when error is proportional to ray length, prioritising closer measurements and leaving distant areas sparse
3238
bool RAYLIB_EXPORT decimateAngular(const std::string &file_stub, double radius_per_length);

0 commit comments

Comments
 (0)