Currently trying to build upon the surface reconstruction tutorial and noticed a potential major issue in the tutorial which, in my experience do generalised outside of it:
In the following tutorial: https://doc.cgal.org/latest/Manual/tuto_reconstruction.html (cgal 5.3), the author do some pre-processing before going into the mesh reconstruction, stuffs like outlier_removal, grid_simplify etc.
However I noticed that no points are being removed during these steps. So I tried multiple parameters in the outlier_removal/grid_simplify and still, everytime, no points gets removed.
However when working with a vector of point instead of a Point_set_3 object, I do manage to get points removed with the same parameters.
Am I the only one who is unable to remove a point with outlier_removal/grid_simplify on a Point_set_3 object?
If yes, can you show me an example how to make it work?
If no, should I avoid using Point_set_3 objects? Or should I convert into a std::vector before doing the pre-processing steps? And how so?
Issue Details
The code runs fine. No errors.
Source Code
This code subset comes straight out of the tutorial.
https://doc.cgal.org/latest/Manual/tuto_reconstruction.html
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Point_set_3.h>
#include <CGAL/Point_set_3/IO.h>
#include <CGAL/remove_outliers.h>
#include <CGAL/grid_simplify_point_set.h>
#include <CGAL/jet_smooth_point_set.h>
#include <CGAL/jet_estimate_normals.h>
#include <CGAL/mst_orient_normals.h>
#include <CGAL/poisson_surface_reconstruction.h>
#include <CGAL/Advancing_front_surface_reconstruction.h>
#include <CGAL/Scale_space_surface_reconstruction_3.h>
#include <CGAL/Scale_space_reconstruction_3/Jet_smoother.h>
#include <CGAL/Scale_space_reconstruction_3/Advancing_front_mesher.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
#include <cstdlib>
#include <vector>
#include <fstream>
// types
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::FT FT;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Vector_3 Vector_3;
typedef Kernel::Sphere_3 Sphere_3;
typedef CGAL::Point_set_3<Point_3, Vector_3> Point_set;
int main(int argc, char*argv[])
{
Point_set points;
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " [input.xyz/off/ply/las]" << std::endl;
return EXIT_FAILURE;
}
const char* input_file = argv[1];
std::ifstream stream (input_file, std::ios_base::binary);
if (!stream)
{
std::cerr << "Error: cannot read file " << input_file << std::endl;
return EXIT_FAILURE;
}
stream >> points;
std::cout << "Read " << points.size () << " point(s)" << std::endl;
if (points.empty())
return EXIT_FAILURE;
CGAL::remove_outliers<CGAL::Sequential_tag>
(points,
24, // Number of neighbors considered for evaluation
points.parameters().threshold_percent (5.0)); // Percentage of points to remove
std::cout << points.number_of_removed_points()
<< " point(s) are outliers." << std::endl;
// Applying point set processing algorithm to a CGAL::Point_set_3
// object does not erase the points from memory but place them in
// the garbage of the object: memory can be freeed by the user.
points.collect_garbage();
// Compute average spacing using neighborhood of 6 points
double spacing = CGAL::compute_average_spacing<CGAL::Sequential_tag> (points, 6);
// Simplify using a grid of size 2 * average spacing
CGAL::grid_simplify_point_set (points, 2. * spacing);
std::cout << points.number_of_removed_points()
<< " point(s) removed after simplification." << std::endl;
points.collect_garbage();
CGAL::jet_smooth_point_set<CGAL::Sequential_tag> (points, 24);
unsigned int reconstruction_choice
= (argc < 3 ? 0 : atoi(argv[2]));
if (reconstruction_choice == 0) // Poisson
{
CGAL::jet_estimate_normals<CGAL::Sequential_tag>
(points, 24); // Use 24 neighbors
// Orientation of normals, returns iterator to first unoriented point
typename Point_set::iterator unoriented_points_begin =
CGAL::mst_orient_normals(points, 24); // Use 24 neighbors
points.remove (unoriented_points_begin, points.end());
return EXIT_SUCCESS;
}
Environment
I've replicated that issue in a debian VM as well as in a docker environment in macos (debian based as well).
Pretty standard stuffs, I'm using the CMakeLists.txt already available in the tutorial_example.cpp folder and running:
Creates files that will show the compiler how to behave
cmake -DCGAL_DIR=/app/cgal -DCMAKE_BUILD_TYPE=Release .
Build the exe
make
I'm a self taught Python programmer so quite new to the C++ stuffs.
Related
I am attempting to create a Voronoi diagram from line segments with the following code.
#include <iostream>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Segment_Delaunay_graph_Linf_filtered_traits_2.h>
#include <CGAL/Segment_Delaunay_graph_Linf_2.h>
#include <CGAL/Voronoi_diagram_2.h>
#include <CGAL/Segment_Delaunay_graph_adaptation_traits_2.h>
#include <CGAL/Segment_Delaunay_graph_adaptation_policies_2.h>
using Traits=CGAL::Segment_Delaunay_graph_Linf_filtered_traits_2<CGAL::Exact_predicates_inexact_constructions_kernel>;
using DelaunayGraph=CGAL::Segment_Delaunay_graph_Linf_2<Traits>;
using AdaptationTraits=CGAL::Segment_Delaunay_graph_adaptation_traits_2<DelaunayGraph>;
using AdaptationPolicy=CGAL::Segment_Delaunay_graph_degeneracy_removal_policy_2<DelaunayGraph>;
using VoronoiDiagram=CGAL::Voronoi_diagram_2<DelaunayGraph,AdaptationTraits,AdaptationPolicy>;
using VoronoiSite=AdaptationTraits::Site_2;
using VoronoiPoint=VoronoiSite::Point_2;
int main(int argc, char** argv)
{
VoronoiDiagram vd;
VoronoiPoint pt0(0.0, 0.0), pt1(5.0, 0.0), pt2(2.0, 2.0), pt3(4.0, 4.0);
vd.insert(VoronoiSite::construct_site_2(pt0, pt1));
vd.insert(VoronoiSite::construct_site_2(pt2, pt3));
int c = 0;
for (auto it = vd.edges_begin(); it != vd.edges_end(); it++)
{
std::cout << "Edge #" << c++ << std::endl;
if (it->has_source())
std::cout << "\t" << it->source()->point();
else
std::cout << "\tInfinity";
std::cout << std::endl;
if (it->has_target())
std::cout << "\t" << it->target()->point();
else
std::cout << "\tInfinity";
std::cout << std::endl;
}
return 0;
}
The output starts with
Edge #0
0 2
Infinity
Edge #1
6 2
Infinity
Edge #2
5 1.66667
Infinity
...
This is my custom visualization of what it looks like.
I expect Edge #1 to be the edge that is equidistant to (4, 4) and (5, 0), however the point (6, 2) is NOT equidistant to those two points. I expect that point to be roughly (5.7, 2.3).
Based on edge 2, I know that ALL the numbers are not integers, but it seems like some of them are being rounded or something. To be clear, I very likely lack some very basic CGAL/Kernel knowledge. I tried swapping in the Cartesian<double> kernel, but that did not change the result.
To follow up on Marc's comment, I needed to change the definitions at the top. The result that works is
#include <iostream>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Segment_Delaunay_graph_filtered_traits_2.h>
#include <CGAL/Segment_Delaunay_graph_2.h>
#include <CGAL/Voronoi_diagram_2.h>
#include <CGAL/Segment_Delaunay_graph_adaptation_traits_2.h>
#include <CGAL/Segment_Delaunay_graph_adaptation_policies_2.h>
using CartesianKernel=CGAL::Simple_cartesian<double>;
using Traits=CGAL::Segment_Delaunay_graph_filtered_traits_2<CartesianKernel,CGAL::Field_with_sqrt_tag>;
using DelaunayGraph=CGAL::Segment_Delaunay_graph_2<Traits>;
using AdaptationTraits=CGAL::Segment_Delaunay_graph_adaptation_traits_2<DelaunayGraph>;
using AdaptationPolicy=CGAL::Segment_Delaunay_graph_degeneracy_removal_policy_2<DelaunayGraph>;
using VoronoiDiagram=CGAL::Voronoi_diagram_2<DelaunayGraph,AdaptationTraits,AdaptationPolicy>;
using VoronoiSite=AdaptationTraits::Site_2;
using VoronoiPoint=VoronoiSite::Point_2;
I read here that LibTIFF can display floating point TIFFs. However, I would like to load an image, then get the float values as an array.
Is this possible to do using LibTIFF?
Example TIFF
EDIT: I am using RHEL 6.
If you want to do it with pure libTIFF, your code might look something like this - note that I have not done much error checking so as not to confuse the reader of the code - but you should check that the image is of type float, and you should check the results of memory allocations and you probably shouldn't use malloc() like I do but rather the new C++ methods of memory allocation - but the concept is hopefully clear and the code generates the same answers as my CImg version...
#include "tiffio.h"
#include <cstdio>
#include <iostream>
using namespace std;
int main()
{
TIFF* tiff = TIFFOpen("image.tif","r");
if (!tiff) {
cerr << "Failed to open image" << endl;
exit(1);
}
uint32 width, height;
tsize_t scanlength;
// Read dimensions of image
if (TIFFGetField(tiff,TIFFTAG_IMAGEWIDTH,&width) != 1) {
cerr << "Failed to read width" << endl;
exit(1);
}
if (TIFFGetField(tiff,TIFFTAG_IMAGELENGTH, &height) != 1) {
cerr << "Failed to read height" << endl;
exit(1);
}
scanlength = TIFFScanlineSize(tiff);
// Make space for image in memory
float** image= (float**)malloc(sizeof (float*)*height);
cout << "Dimensions: " << width << "x" << height << endl;
cout << "Line buffer length (bytes): " << scanlength << endl;
// Read image data allocating space for each line as we get it
for (uint32 y = 0; y < height; y++) {
image[y]=(float*)malloc(scanlength);
TIFFReadScanline(tiff,image[y],y);
cout << "Line(" << y << "): " << image[y][0] << "," << image[y][1] << "," << image[y][2] << endl;
}
TIFFClose(tiff);
}
Sample Output
Dimensions: 512x256
Line buffer length (bytes): 6144
Line(0): 3.91318e-06,0.232721,128
Line(1): 0.24209,1.06866,128
Line(2): 0.185419,2.45852,128
Line(3): 0.141297,3.06488,128
Line(4): 0.346642,4.35358,128
...
...
By the way...
I converted your image to a regular JPEG using ImageMagick in the Terminal at the command line as follows:
convert map.tif[0] -auto-level result.jpg
Yes but you will have a much easier time with this if you use the OpenCV library.
If you have OpenCV library compiled and installed doing what you are asking is as easy as using the imread() function. This saves it to an object called cv::Mat (aka a matrix) with the same dimensions and values as the tiff.
From there you can do just about anything you want with it.
You can do it with LibTIFF, and I may well add an answer based on that later, but for ease of installation and use, I would look at CImg which is a C++ header-only library that is very powerful and ideal for your purposes. As it is header-only, it is simple to include (just one file) and needs no special linking or building.
Here is how you might read a TIFF of RGB floats:
#define cimg_display 0
#define cimg_use_tiff
#include "CImg.h"
#include <iostream>
using namespace cimg_library;
using namespace std;
int main(){
// Read in an image
CImg <float>img("image.tif");
// Get its width and height and tell user
int w=img.width();
int h=img.height();
cout << "Dimensions: " << w << "x" << h << endl;
// Get pointer to buffer/array of floats
float* buffer = img.data();
cout << buffer[0] << "," << buffer[1] << "," << buffer[2] << endl;
}
That prints the first three red pixels because they are arranged in planes - i.e. all the red pixels first, then all the green pixel then all the blue pixels.
You would compile that with:
g++-6 -std=c++11 read.cpp -I/usr/local/include -L/usr/local/lib -ltiff -o read
If you prefer, you can access the pixels a slightly different way like this:
#define cimg_display 0
#define cimg_use_tiff
#include "CImg.h"
#include <iostream>
using namespace cimg_library;
using namespace std;
int main(){
// Read in an image
CImg <float>img("image.tif");
// Get its width and height and tell user
int w=img.width();
int h=img.height();
cout << "Dimensions: " << w << "x" << h << endl;
// Dump the pixels
for(int y=0;y<h;y++)
for(int x=0;x<w;x++)
cout << x << "," << y << ": "
<< img(x,y,0,0) << "/"
<< img(x,y,0,1) << "/"
<< img(x,y,0,2) << endl;
}
Sample Output
Dimensions: 512x256
0,0: 3.91318e-06/0.232721/128
1,0: 1.06577/0.342173/128
2,0: 2.3778/0.405881/128
3,0: 3.22933/0.137184/128
4,0: 4.26638/0.152943/128
5,0: 5.10948/0.00773837/128
6,0: 6.02352/0.058757/128
7,0: 7.33943/0.02835/128
8,0: 8.33965/0.478541/128
9,0: 9.46735/0.335981/128
10,0: 10.1918/0.340277/128
...
...
For your information, I made the test image file also with CImg like this - basically each red pixel is set to its x-coordinate plus a small random float less than 0.5. each green pixel is set to its y-coordinate plus a small random float less than 0.5 and each blue pixel is set to a mid-tone.
#define cimg_display 0
#define cimg_use_tiff
#define cimg_use_png
#include "CImg.h"
#include <cstdlib>
using namespace cimg_library;
int main(){
const int w=512;
const int h=256;
const int channels=3;
float* buffer = new float[w*h*channels];
float* fp=buffer;
for(int y=0;y<h;y++){
for(int x=0;x<w;x++){
*fp++=x+float(rand())/(2.0*RAND_MAX); // red
}
}
for(int y=0;y<h;y++){
for(int x=0;x<w;x++){
*fp++=y+float(rand())/(2.0*RAND_MAX); // green
}
}
for(int y=0;y<h;y++){
for(int x=0;x<w;x++){
*fp++=128; // blue
}
}
CImg <float>img(buffer,w,h,1,channels);
img.save_tiff("result.tif");
}
Yet another, easily installed, lightweight option would be to use vips. You can convert your 32-bit TIF to a raw file of 32-bit floats and read them straight into your C++ program. At the commandline, do the conversion with
vips rawsave yourImage.tif raw.bin
and then read in the uncompressed, unformatted floats from file raw.bin. If we now dump the file raw.bin, interpreting the data as floats, you can see the same values as in my other answers:
od -f raw.bin
0000000 3.913185e-06 2.327210e-01 1.280000e+02 1.065769e+00
0000020 3.421732e-01 1.280000e+02 2.377803e+00 4.058807e-01
0000040 1.280000e+02 3.229325e+00 1.371841e-01 1.280000e+02
Of course, you can have your program do the conversion by linking to libvips or simply using system() to run the commandline version and then read its output file.
I have a problem. I would like to intersect a quad with a quad.
int main(){
typedef boost::geometry::model::point_xy<double> TBoostPoint;
typedef boost::geometry::model::polygon<TBoostPoint> TBoostPoly;
TBoostPoint point;
TBoostPoly firstPoly, secondPoly;
boost::geometry::read_wkt("POLYGON(
(1.504477611940313, 3.761194029850755),
(1.504477611940305, 3.573134328358203),
(1.316417910447765, 3.573134328358206),
(1.316417910447769, 3.761194029850752))", firstPoly);
boost::geometry::read_wkt("POLYGON(
(1.504477611940313, 3.761194029850755),
(1.504477611940305, 3.573134328358203),
(1.316417910447765, 3.573134328358206),
(1.316417910447751, 3.761194029850769))", secondPoly);
std::vector<TBoostPoly> outPoly;
boost::geometry::intersection(firstPoly,secondPoly,outPoly);
}
outPoly - is empty, but it not so.
There were 2 main issues.
The output is undefined because the input is invalid.
The input WKT specifies a lot of invalid inner rings (consisting of single points), instead of what you expected, a single outer ring of 5 points (excl. closing point). Fix it:
bg::read_wkt("POLYGON(( 1.504477611940313 3.761194029850755, 1.504477611940305 3.573134328358203, 1.316417910447765 3.573134328358206, 1.316417910447769 3.761194029850752))", first);
bg::read_wkt("POLYGON(( 1.504477611940313 3.761194029850755, 1.504477611940305 3.573134328358203, 1.316417910447765 3.573134328358206, 1.316417910447751 3.761194029850769))", second);
Boost Geometry assumes throughout that you never make errors against the documented preconditions. If you read in the polygon concept page and preconditions for intersection you'll see thew full list¹.
If you don't, you get no friendly errors, just silent failure, corruption or just wrong answers. Yeah. That's bad.
What's worse, BGeo didn't even have a is_valid facility to test the bulk of requirements until Boost 1_57 (IIRC). The good news is, if you upgrade to this version or later your life will be much simpler.
In this case you would have learned that the polygons weren't properly closed:
Live On Coliru
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
#include <boost/geometry/io/io.hpp>
#include <boost/geometry/algorithms/intersection.hpp>
#include <boost/geometry/algorithms/correct.hpp>
#include <boost/geometry/algorithms/is_valid.hpp>
namespace bg = boost::geometry;
int main(){
typedef bg::model::d2::point_xy<double> TPoint;
typedef bg::model::polygon<TPoint> TPoly;
TPoly first, second;
bg::read_wkt("POLYGON(( 1.504477611940313 3.761194029850755, 1.504477611940305 3.573134328358203, 1.316417910447765 3.573134328358206, 1.316417910447769 3.761194029850752))", first);
bg::read_wkt("POLYGON(( 1.504477611940313 3.761194029850755, 1.504477611940305 3.573134328358203, 1.316417910447765 3.573134328358206, 1.316417910447751 3.761194029850769))", second);
std::string reason;
// polys not closed!
if (!bg::is_valid(first, reason)) std::cout << "First polygon not valid: " << reason << "\n";
if (!bg::is_valid(second, reason)) std::cout << "Second polygon not valid: " << reason << "\n";
bg::correct(first);
bg::correct(second);
// no more output!
if (!bg::is_valid(first, reason)) std::cout << "First polygon not valid: " << reason << "\n";
if (!bg::is_valid(second, reason)) std::cout << "Second polygon not valid: " << reason << "\n";
std::vector<TPoly> out;
bg::intersection(first, second, out);
for (auto& g : out)
std::cout << "\nresult: " << bg::wkt(g) << "\n";
}
Prints:
First polygon not valid: Geometry is defined as closed but is open
Second polygon not valid: Geometry is defined as closed but is open
Oops. The geos weren't closed! correct(poly) fixes this for us on auto-pilot:
result: POLYGON((1.50448 3.57313,1.31642 3.57313,1.31642 3.76119,1.50448 3.76119,1.50448 3.57313))
¹ outer ring must be counter clockwise, inner cw, polys must be closed... stuff like that.
Good night. Has anyone encountered a similar problem?
Constructing Voronoi diagram has not caused problems. Voronoi cell is a polygon, at least for me. The library also allows you to find the distance from a point to a polygon. But the library function does not want to work with the cell. The compiler produces something in Elvish. Joke. In short, the compiler output can not help me.
Is there a way to make a polygon from the cell?
Voronoi diagram is constructed on vpoints. The program should calculate the distance from the qpoints element to the corresponding cell.
Here is my code:
#include <iostream>
#include <vector>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/polygon/voronoi.hpp>
namespace bg = boost::geometry;
using boost::polygon::voronoi_diagram;
typedef voronoi_diagram<double>::cell_type cell_type;
typedef voronoi_diagram<double>::edge_type edge_type;
typedef voronoi_diagram<double>::vertex_type vertex_type;
typedef boost::polygon::point_data<double> point_type;
using namespace std;
int main() {
vector< point_type > vpoints;
vpoints.push_back(point_type(0.0, 0.0));
vpoints.push_back(point_type(0.0, 4.0));
vpoints.push_back(point_type(4.0, 4.0));
vpoints.push_back(point_type(4.0, 0.0));
vpoints.push_back(point_type(2.0, 2.0));
vector< point_type > qpoints;
qpoints.push_back(point_type(0.0, 0.0));
qpoints.push_back(point_type(0.0, 2.0));
qpoints.push_back(point_type(3.0, 3.0));
qpoints.push_back(point_type(5.0, 5.0));
qpoints.push_back(point_type(5.0, 5.0));
voronoi_diagram<double> vd;
construct_voronoi(vpoints.begin(), vpoints.end(), &vd);
for (int i = 0; i < qpoints.size(); i++) {
for (voronoi_diagram<double>::const_cell_iterator it = vd.cells().begin();
it != vd.cells().end(); ++it) {
if (i == it->source_index()) {
cout << "v[i]=(" << vpoints[i].x() << "," << vpoints[i].y() << ")\t";
cout << "q[i]=(" << qpoints[i].x() << "," << qpoints[i].y() << ")\t";
cout << "Distance=";
cout << bg::distance(qpoints[i], *it) << endl;
cout << endl;
break;
}
}
}
return 0;
}
The message is
boost_1_57_0/boost/geometry/core/geometry_id.hpp|37 col 5| error: no matching function for call to ‘assertion_failed(mpl_::failed************ (boost::geometry::core_dispatch::geometry_id<void>::NOT_IMPLEMENTED_FOR_THIS_GEOMETRY_TYPE::************)(mpl_::assert_::types<void, mpl_::na, mpl_::na, mpl_::na>))’
Which is the NOT_IMPLEMENTED_FOR_THIS_GEOMETRY_TYPE assert. It happens doing geometry_id for reverse_dispatch:
/*!
\brief Meta-function returning the id of a geometry type
\details The meta-function geometry_id defines a numerical ID (based on
boost::mpl::int_<...> ) for each geometry concept. A numerical ID is
sometimes useful, and within Boost.Geometry it is used for the
reverse_dispatch metafuntion.
\note Used for e.g. reverse meta-function
\ingroup core
*/
template <typename Geometry>
struct geometry_id : core_dispatch::geometry_id<typename tag<Geometry>::type>
{};
The same warning is triggered when you do
cout << distance(qpoints[i], qpoints[i]) << endl;
So the problem is that your point type is not a reqistered geometry. Including
#include <boost/geometry/geometries/adapted/boost_polygon.hpp>
makes that compile, but of course
cout << distance(qpoints[i], *it) << endl;
still fails, this time because const boost::polygon::voronoi_cell<double> isn't a known geometry type to Boost Geometry.
I'd suggest not mixing the libraries unless you know why you want to.
It looks to me that a voronoi cell can be more than just a single thing (contains_segment() and contains_point() are indications). You may have to write some switching logic to handle the possible cases separately, and perhaps use euclidean_distance from Boost Polygon in the process (as opposed to boost::geometry::distance`)
here is my problem: I have a 2D matrix of doubles containing data. The data is gaussian and and i need to find out which datapoints are the extrem ones. As a first estimation, values > (µ + 3 sigma) should be okay. Just to be sure whether i'm corret with doing the following:
I can add the data to the accumulator, i'm able to calculate the µ, but how can i get the f** sigma?
you can get mean and moment from accumulator:
#include <iostream>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/moment.hpp>
using namespace boost::accumulators;
int main()
{
// Define an accumulator set for calculating the mean and the
// 2nd moment ...
accumulator_set<double, stats<tag::mean, tag::moment<2> > > acc;
// push in some data ...
acc(1.2);
acc(2.3);
acc(3.4);
acc(4.5);
// Display the results ...
std::cout << "Mean: " << mean(acc) << std::endl;
std::cout << "Moment: " << accumulators::moment<2>(acc) << std::endl;
return 0;
}
However in the boost docs we read that this is raw moment (not central):
Calculates the N-th moment of the samples, which is defined as the sum
of the N-th power of the samples over the count of samples.
so you need to adjust this and here is how to do it (you need sqrt of second central moment, mi_2).
http://en.wikipedia.org/wiki/Moment_(mathematics)