// The P2G writer will only work with a single point view at the current time. // Merge point views before writing. void P2gWriter::write(const PointViewPtr view) { view->calculateBounds(m_bounds); m_GRID_SIZE_X = (int)(ceil((m_bounds.maxx - m_bounds.minx) / m_GRID_DIST_X)) + 1; m_GRID_SIZE_Y = (int)(ceil((m_bounds.maxy - m_bounds.miny) / m_GRID_DIST_Y)) + 1; log()->floatPrecision(6); log()->get(LogLevel::Debug) << "X grid distance: " << m_GRID_DIST_X << std::endl; log()->get(LogLevel::Debug) << "Y grid distance: " << m_GRID_DIST_Y << std::endl; log()->clearFloat(); m_interpolator.reset(new InCoreInterp(m_GRID_DIST_X, m_GRID_DIST_Y, m_GRID_SIZE_X, m_GRID_SIZE_Y, m_RADIUS * m_RADIUS, m_bounds.minx, m_bounds.maxx, m_bounds.miny, m_bounds.maxy, m_fill_window_size)); m_interpolator->init(); for (point_count_t idx = 0; idx < view->size(); idx++) { double x = view->getFieldAs<double>(Dimension::Id::X, idx) - m_bounds.minx; double y = view->getFieldAs<double>(Dimension::Id::Y, idx) - m_bounds.miny; double z = view->getFieldAs<double>(Dimension::Id::Z, idx); if (m_interpolator->update(x, y, z) < 0) { std::ostringstream oss; oss << getName() << ": interp->update() error while processing"; throw pdal_error(oss.str()); } } }
void NitfWriter::writeView(const PointViewPtr view) { //ABELL - Think we can just get this from the LAS file header // when we're done. view->calculateBounds(m_bounds); LasWriter::writeView(view); }
void PointView::calculateBounds(const PointViewSet& set, BOX3D& output) { for (auto iter = set.begin(); iter != set.end(); ++iter) { PointViewPtr buf = *iter; buf->calculateBounds(output); } }
void P2gWriter::write(const PointViewPtr view) { for (point_count_t idx = 0; idx < view->size(); idx++) { double x = view->getFieldAs<double>(Dimension::Id::X, idx); double y = view->getFieldAs<double>(Dimension::Id::Y, idx); double z = view->getFieldAs<double>(Dimension::Id::Z, idx); m_coordinates.push_back(Coordinate{x, y, z}); } view->calculateBounds(m_bounds); }
void PcdWriter::write(const PointViewPtr view) { pcl::PointCloud<XYZIRGBA>::Ptr cloud(new pcl::PointCloud<XYZIRGBA>); BOX3D buffer_bounds; view->calculateBounds(buffer_bounds); pclsupport::PDALtoPCD(view, *cloud, buffer_bounds); pcl::PCDWriter w; if (m_compressed) w.writeBinaryCompressed<XYZIRGBA>(m_filename, *cloud); else w.writeASCII<XYZIRGBA>(m_filename, *cloud); }
TEST(SplitterTest, test_buffer) { Options readerOptions; readerOptions.add("filename", Support::datapath("las/1.2-with-color.las")); LasReader reader; reader.setOptions(readerOptions); Options splitterOptions; splitterOptions.add("length", 1000); splitterOptions.add("buffer", 20); SplitterFilter splitter; splitter.setOptions(splitterOptions); splitter.setInput(reader); PointTable table; splitter.prepare(table); PointViewSet viewSet = splitter.execute(table); std::vector<PointViewPtr> views; std::map<PointViewPtr, BOX2D> bounds; for (auto it = viewSet.begin(); it != viewSet.end(); ++it) { BOX2D b; PointViewPtr v = *it; v->calculateBounds(b); EXPECT_TRUE(b.maxx - b.minx <= 1040); EXPECT_TRUE(b.maxy - b.miny <= 1040); bounds[v] = b; views.push_back(v); } auto sorter = [&bounds](PointViewPtr p1, PointViewPtr p2) { BOX2D b1 = bounds[p1]; BOX2D b2 = bounds[p2]; return b1.minx < b2.minx ? true : b1.minx > b2.minx ? false : b1.miny < b2.miny; }; std::sort(views.begin(), views.end(), sorter); EXPECT_EQ(views.size(), 24u); size_t counts[] = {26, 26, 3, 28, 27, 13, 14, 65, 80, 47, 80, 89, 94, 77, 5, 79, 65, 34, 63, 67, 74, 69, 36, 5}; size_t i = 0; for (PointViewPtr view : views) EXPECT_EQ(view->size(), counts[i++]); }
void P2gWriter::write(const PointViewPtr view) { std::string z_name = getOptions().getValueOrDefault<std::string>("Z", "Z"); for (point_count_t idx = 0; idx < view->size(); idx++) { double x = view->getFieldAs<double>(Dimension::Id::X, idx); double y = view->getFieldAs<double>(Dimension::Id::Y, idx); double z = view->getFieldAs<double>(Dimension::Id::Z, idx); m_coordinates.push_back(boost::tuple<double, double, double>(x, y, z)); } view->calculateBounds(m_bounds); }
PointViewSet VoxelGridFilter::run(PointViewPtr input) { PointViewPtr output = input->makeNew(); PointViewSet viewSet; viewSet.insert(output); bool logOutput = log()->getLevel() > LogLevel::Debug1; if (logOutput) log()->floatPrecision(8); log()->get(LogLevel::Debug2) << "Process VoxelGridFilter..." << std::endl; BOX3D buffer_bounds; input->calculateBounds(buffer_bounds); // convert PointView to PointNormal typedef pcl::PointCloud<pcl::PointXYZ> Cloud; Cloud::Ptr cloud(new Cloud); pclsupport::PDALtoPCD(input, *cloud, buffer_bounds); pclsupport::setLogLevel(log()->getLevel()); // initial setup pcl::VoxelGrid<pcl::PointXYZ> vg; vg.setInputCloud(cloud); vg.setLeafSize(m_leaf_x, m_leaf_y, m_leaf_z); // create PointCloud for results Cloud::Ptr cloud_f(new Cloud); vg.filter(*cloud_f); if (cloud_f->points.empty()) { log()->get(LogLevel::Debug2) << "Filtered cloud has no points!" << std::endl; return viewSet; } pclsupport::PCDtoPDAL(*cloud_f, output, buffer_bounds); log()->get(LogLevel::Debug2) << cloud->points.size() << " before, " << cloud_f->points.size() << " after" << std::endl; log()->get(LogLevel::Debug2) << output->size() << std::endl; return viewSet; }
PointViewSet RadiusOutlierFilter::run(PointViewPtr input) { bool logOutput = log()->getLevel() > LogLevel::Debug1; if (logOutput) log()->floatPrecision(8); log()->get(LogLevel::Debug2) << "Process RadiusOutlierFilter...\n"; // convert PointView to PointXYZ typedef pcl::PointCloud<pcl::PointXYZ> Cloud; Cloud::Ptr cloud(new Cloud); BOX3D bounds; input->calculateBounds(bounds); pclsupport::PDALtoPCD(input, *cloud, bounds); pclsupport::setLogLevel(log()->getLevel()); // setup the outlier filter pcl::RadiusOutlierRemoval<pcl::PointXYZ> ror(true); ror.setInputCloud(cloud); ror.setMinNeighborsInRadius(m_min_neighbors); ror.setRadiusSearch(m_radius); pcl::PointCloud<pcl::PointXYZ> output; ror.setNegative(true); ror.filter(output); // filtered to return inliers pcl::PointIndicesPtr inliers(new pcl::PointIndices); ror.getRemovedIndices(*inliers); PointViewSet viewSet; if (inliers->indices.empty()) { log()->get(LogLevel::Warning) << "Requested filter would remove all points. Try a larger radius/smaller minimum neighbors.\n"; viewSet.insert(input); return viewSet; } // inverse are the outliers std::vector<int> outliers(input->size()-inliers->indices.size()); for (PointId i = 0, j = 0, k = 0; i < input->size(); ++i) { if (i == (PointId)inliers->indices[j]) { j++; continue; } outliers[k++] = i; } if (!outliers.empty() && (m_classify || m_extract)) { if (m_classify) { log()->get(LogLevel::Debug2) << "Labeled " << outliers.size() << " outliers as noise!\n"; // set the classification label of outlier returns as 18 // (corresponding to ASPRS LAS specification for high noise) for (const auto& i : outliers) { input->setField(Dimension::Id::Classification, i, 18); } viewSet.insert(input); } if (m_extract) { log()->get(LogLevel::Debug2) << "Extracted " << inliers->indices.size() << " inliers!\n"; // create new PointView containing only outliers PointViewPtr output = input->makeNew(); for (const auto& i : inliers->indices) { output->appendPoint(*input, i); } viewSet.erase(input); viewSet.insert(output); } } else { if (outliers.empty()) log()->get(LogLevel::Warning) << "Filtered cloud has no outliers!\n"; if (!(m_classify || m_extract)) log()->get(LogLevel::Warning) << "Must choose --classify or --extract\n"; // return the input buffer unchanged viewSet.insert(input); } return viewSet; }
std::vector<PointId> PMFFilter::processGroundApprox(PointViewPtr view) { using namespace Eigen; BOX2D bounds; view->calculateBounds(bounds); double extent_x = floor(bounds.maxx) - ceil(bounds.minx); double extent_y = floor(bounds.maxy) - ceil(bounds.miny); int cols = static_cast<int>(ceil(extent_x/m_cellSize)) + 1; int rows = static_cast<int>(ceil(extent_y/m_cellSize)) + 1; // Compute the series of window sizes and height thresholds std::vector<float> htvec; std::vector<float> wsvec; int iter = 0; float ws = 0.0f; float ht = 0.0f; while (ws < m_maxWindowSize) { // Determine the initial window size. if (1) // exponential ws = m_cellSize * (2.0f * std::pow(2, iter) + 1.0f); else ws = m_cellSize * (2.0f * (iter+1) * 2 + 1.0f); // Calculate the height threshold to be used in the next iteration. if (iter == 0) ht = m_initialDistance; else ht = m_slope * (ws - wsvec[iter-1]) * m_cellSize + m_initialDistance; // Enforce max distance on height threshold if (ht > m_maxDistance) ht = m_maxDistance; wsvec.push_back(ws); htvec.push_back(ht); iter++; } std::vector<PointId> groundIdx; for (PointId i = 0; i < view->size(); ++i) groundIdx.push_back(i); MatrixXd ZImin = eigen::createDSM(*view.get(), rows, cols, m_cellSize, bounds); // Progressively filter ground returns using morphological open for (size_t j = 0; j < wsvec.size(); ++j) { log()->get(LogLevel::Debug) << "Iteration " << j << " (height threshold = " << htvec[j] << ", window size = " << wsvec[j] << ")...\n"; MatrixXd mo = eigen::matrixOpen(ZImin, 0.5*(wsvec[j]-1)); std::vector<PointId> groundNewIdx; for (auto p_idx : groundIdx) { double x = view->getFieldAs<double>(Dimension::Id::X, p_idx); double y = view->getFieldAs<double>(Dimension::Id::Y, p_idx); double z = view->getFieldAs<double>(Dimension::Id::Z, p_idx); int r = static_cast<int>(std::floor((y-bounds.miny) / m_cellSize)); int c = static_cast<int>(std::floor((x-bounds.minx) / m_cellSize)); float diff = z - mo(r, c); if (diff < htvec[j]) groundNewIdx.push_back(p_idx); } ZImin.swap(mo); groundIdx.swap(groundNewIdx); log()->get(LogLevel::Debug) << "Ground now has " << groundIdx.size() << " points.\n"; } return groundIdx; }
std::vector<PointId> SMRFilter::processGround(PointViewPtr view) { log()->get(LogLevel::Info) << "processGround: Running SMRF...\n"; // The algorithm consists of four conceptually distinct stages. The first is // the creation of the minimum surface (ZImin). The second is the processing // of the minimum surface, in which grid cells from the raster are // identified as either containing bare earth (BE) or objects (OBJ). This // second stage represents the heart of the algorithm. The third step is the // creation of a DEM from these gridded points. The fourth step is the // identification of the original LIDAR points as either BE or OBJ based on // their relationship to the interpolated std::vector<PointId> groundIdx; BOX2D bounds; view->calculateBounds(bounds); SpatialReference srs(view->spatialReference()); // Determine the number of rows and columns at the given cell size. m_numCols = ((bounds.maxx - bounds.minx) / m_cellSize) + 1; m_numRows = ((bounds.maxy - bounds.miny) / m_cellSize) + 1; MatrixXd cx(m_numRows, m_numCols); MatrixXd cy(m_numRows, m_numCols); for (auto c = 0; c < m_numCols; ++c) { for (auto r = 0; r < m_numRows; ++r) { cx(r, c) = bounds.minx + (c + 0.5) * m_cellSize; cy(r, c) = bounds.miny + (r + 0.5) * m_cellSize; } } // STEP 1: // As with many other ground filtering algorithms, the first step is // generation of ZImin from the cell size parameter and the extent of the // data. The two vectors corresponding to [min:cellSize:max] for each // coordinate – xi and yi – may be supplied by the user or may be easily and // automatically calculated from the data. Without supplied ranges, the SMRF // algorithm creates a raster from the ceiling of the minimum to the floor // of the maximum values for each of the (x,y) dimensions. If the supplied // cell size parameter is not an integer, the same general rule applies to // values evenly divisible by the cell size. For example, if cell size is // equal to 0.5 m, and the x values range from 52345.6 to 52545.4, the range // would be [52346 52545]. // The minimum surface grid ZImin defined by vectors (xi,yi) is filled with // the nearest, lowest elevation from the original point cloud (x,y,z) // values, provided that the distance to the nearest point does not exceed // the supplied cell size parameter. This provision means that some grid // points of ZImin will go unfilled. To fill these values, we rely on // computationally inexpensive image inpainting techniques. Image inpainting // involves the replacement of the empty cells in an image (or matrix) with // values calculated from other nearby values. It is a type of interpolation // technique derived from artistic replacement of damaged portions of // photographs and paintings, where preservation of texture is an important // concern (Bertalmio et al., 2000). When empty values are spread through // the image, and the ratio of filled to empty pixels is quite high, most // methods of inpainting will produce satisfactory results. In an evaluation // of inpainting methods on ground identification from the final terrain // model, we found that Laplacian techniques produced error rates nearly // three times higher than either an average of the eight nearest neighbors // or D’Errico’s spring-metaphor inpainting technique (D’Errico, 2004). The // spring-metaphor technique imagines springs connecting each cell with its // eight adjacent neighbors, where the inpainted value corresponds to the // lowest energy state of the set, and where the entire (sparse) set of // linear equations is solved using partial differential equations. Both of // these latter techniques were nearly the same with regards to total error, // with the spring technique performing slightly better than the k-nearest // neighbor (KNN) approach. MatrixXd ZImin = eigen::createMinMatrix(*view.get(), m_numRows, m_numCols, m_cellSize, bounds); // MatrixXd ZImin_painted = inpaintKnn(cx, cy, ZImin); // MatrixXd ZImin_painted = TPS(cx, cy, ZImin); MatrixXd ZImin_painted = expandingTPS(cx, cy, ZImin); if (!m_outDir.empty()) { std::string filename = FileUtils::toAbsolutePath("zimin.tif", m_outDir); eigen::writeMatrix(ZImin, filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("zimin_painted.tif", m_outDir); eigen::writeMatrix(ZImin_painted, filename, "GTiff", m_cellSize, bounds, srs); } ZImin = ZImin_painted; // STEP 2: // The second stage of the ground identification algorithm involves the // application of a progressive morphological filter to the minimum surface // grid (ZImin). At the first iteration, the filter applies an image opening // operation to the minimum surface. An opening operation consists of an // application of an erosion filter followed by a dilation filter. The // erosion acts to snap relative high values to relative lows, where a // supplied window radius and shape (or structuring element) defines the // search neighborhood. The dilation uses the same window radius and // structuring element, acting to outwardly expand relative highs. Fig. 2 // illustrates an opening operation on a cross section of a transect from // Sample 1–1 in the ISPRS LIDAR reference dataset (Sithole and Vosselman, // 2003), following Zhang et al. (2003). // paper has low point happening later, i guess it doesn't matter too much, this is where he does it in matlab code MatrixXi Low = progressiveFilter(-ZImin, m_cellSize, 5.0, 1.0); // matlab code has net cutting occurring here MatrixXd ZInet = ZImin; MatrixXi isNetCell = MatrixXi::Zero(m_numRows, m_numCols); if (m_cutNet > 0.0) { MatrixXd bigOpen = eigen::matrixOpen(ZImin, 2*std::ceil(m_cutNet / m_cellSize)); for (auto c = 0; c < m_numCols; c += std::ceil(m_cutNet/m_cellSize)) { for (auto r = 0; r < m_numRows; ++r) { isNetCell(r, c) = 1; } } for (auto c = 0; c < m_numCols; ++c) { for (auto r = 0; r < m_numRows; r += std::ceil(m_cutNet/m_cellSize)) { isNetCell(r, c) = 1; } } for (auto c = 0; c < m_numCols; ++c) { for (auto r = 0; r < m_numRows; ++r) { if (isNetCell(r, c)==1) ZInet(r, c) = bigOpen(r, c); } } } // and finally object detection MatrixXi Obj = progressiveFilter(ZInet, m_cellSize, m_percentSlope, m_maxWindow); // STEP 3: // The end result of the iteration process described above is a binary grid // where each cell is classified as being either bare earth (BE) or object // (OBJ). The algorithm then applies this mask to the starting minimum // surface to eliminate nonground cells. These cells are then inpainted // according to the same process described previously, producing a // provisional DEM (ZIpro). // we currently aren't checking for net cells or empty cells (haven't i already marked empty cells as NaNs?) MatrixXd ZIpro = ZImin; for (int i = 0; i < Obj.size(); ++i) { if (Obj(i) == 1 || Low(i) == 1 || isNetCell(i) == 1) ZIpro(i) = std::numeric_limits<double>::quiet_NaN(); } // MatrixXd ZIpro_painted = inpaintKnn(cx, cy, ZIpro); // MatrixXd ZIpro_painted = TPS(cx, cy, ZIpro); MatrixXd ZIpro_painted = expandingTPS(cx, cy, ZIpro); if (!m_outDir.empty()) { std::string filename = FileUtils::toAbsolutePath("zilow.tif", m_outDir); eigen::writeMatrix(Low.cast<double>(), filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("zinet.tif", m_outDir); eigen::writeMatrix(ZInet, filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("ziobj.tif", m_outDir); eigen::writeMatrix(Obj.cast<double>(), filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("zipro.tif", m_outDir); eigen::writeMatrix(ZIpro, filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("zipro_painted.tif", m_outDir); eigen::writeMatrix(ZIpro_painted, filename, "GTiff", m_cellSize, bounds, srs); } ZIpro = ZIpro_painted; // STEP 4: // The final step of the algorithm is the identification of ground/object // LIDAR points. This is accomplished by measuring the vertical distance // between each LIDAR point and the provisional DEM, and applying a // threshold calculation. While many authors use a single value for the // elevation threshold, we suggest that a second parameter be used to // increase the threshold on steep slopes, transforming the threshold to a // slope-dependent value. The total permissible distance is then equal to a // fixed elevation threshold plus the scaling value multiplied by the slope // of the DEM at each LIDAR point. The rationale behind this approach is // that small horizontal and vertical displacements yield larger errors on // steep slopes, and as a result the BE/OBJ threshold distance should be // more per- missive at these points. // The calculation requires that both elevation and slope are interpolated // from the provisional DEM. There are any number of interpolation // techniques that might be used, and even nearest neighbor approaches work // quite well, so long as the cell size of the DEM nearly corresponds to the // resolution of the LIDAR data. A comparison of how well these different // methods of interpolation perform is given in the next section. Based on // these results, we find that a splined cubic interpolation provides the // best results. // It is common in LIDAR point clouds to have a small number of outliers // which may be either above or below the terrain surface. While // above-ground outliers (e.g., a random return from a bird in flight) are // filtered during the normal algorithm routine, the below-ground outliers // (e.g., those caused by a reflection) require a separate approach. Early // in the routine and along a separate processing fork, the minimum surface // is checked for low outliers by inverting the point cloud in the z-axis // and applying the filter with parameters (slope = 500%, maxWindowSize = // 1). The resulting mask is used to flag low outlier cells as OBJ before // the inpainting of the provisional DEM. This outlier identification // methodology is functionally the same as that of Zhang et al. (2003). // The provisional DEM (ZIpro), created by removing OBJ cells from the // original minimum surface (ZImin) and then inpainting, tends to be less // smooth than one might wish, especially when the surfaces are to be used // to create visual products like immersive geographic virtual environments. // As a result, it is often worthwhile to reinter- polate a final DEM from // the identified ground points of the original LIDAR data (ZIfin). Surfaces // created from these data tend to be smoother and more visually satisfying // than those derived from the provisional DEM. // Very large (>40m in length) buildings can sometimes prove troublesome to // remove on highly differentiated terrain. To accommodate the removal of // such objects, we implemented a feature in the published SMRF algorithm // which is helpful in removing such features. We accomplish this by // introducing into the initial minimum surface a ‘‘net’’ of minimum values // at a spacing equal to the maximum window diameter, where these minimum // values are found by applying a morphological open operation with a disk // shaped structuring element of radius (2?wkmax). Since only one example in // this dataset had features this large (Sample 4–2, a trainyard) we did not // include this portion of the algorithm in the formal testing procedure, // though we provide a brief analysis of the effect of using this net filter // in the next section. MatrixXd scaled = ZIpro / m_cellSize; MatrixXd gx = eigen::gradX(scaled); MatrixXd gy = eigen::gradY(scaled); MatrixXd gsurfs = (gx.cwiseProduct(gx) + gy.cwiseProduct(gy)).cwiseSqrt(); // MatrixXd gsurfs_painted = inpaintKnn(cx, cy, gsurfs); // MatrixXd gsurfs_painted = TPS(cx, cy, gsurfs); MatrixXd gsurfs_painted = expandingTPS(cx, cy, gsurfs); if (!m_outDir.empty()) { std::string filename = FileUtils::toAbsolutePath("gx.tif", m_outDir); eigen::writeMatrix(gx, filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("gy.tif", m_outDir); eigen::writeMatrix(gy, filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("gsurfs.tif", m_outDir); eigen::writeMatrix(gsurfs, filename, "GTiff", m_cellSize, bounds, srs); filename = FileUtils::toAbsolutePath("gsurfs_painted.tif", m_outDir); eigen::writeMatrix(gsurfs_painted, filename, "GTiff", m_cellSize, bounds, srs); } gsurfs = gsurfs_painted; MatrixXd thresh = (m_threshold + m_scalar * gsurfs.array()).matrix(); if (!m_outDir.empty()) { std::string filename = FileUtils::toAbsolutePath("thresh.tif", m_outDir); eigen::writeMatrix(thresh, filename, "GTiff", m_cellSize, bounds, srs); } for (PointId i = 0; i < view->size(); ++i) { using namespace Dimension; double x = view->getFieldAs<double>(Id::X, i); double y = view->getFieldAs<double>(Id::Y, i); double z = view->getFieldAs<double>(Id::Z, i); int c = Utils::clamp(static_cast<int>(floor(x - bounds.minx) / m_cellSize), 0, m_numCols-1); int r = Utils::clamp(static_cast<int>(floor(y - bounds.miny) / m_cellSize), 0, m_numRows-1); // author uses spline interpolation to get value from ZIpro and gsurfs if (std::isnan(ZIpro(r, c))) continue; // not sure i should just brush this under the rug... if (std::isnan(gsurfs(r, c))) continue; double ez = ZIpro(r, c); // double ez = interp2(r, c, cx, cy, ZIpro); // double si = gsurfs(r, c); // double si = interp2(r, c, cx, cy, gsurfs); // double reqVal = m_threshold + 1.2 * si; if (std::abs(ez - z) > thresh(r, c)) continue; // if (std::abs(ZIpro(r, c) - z) > m_threshold) // continue; groundIdx.push_back(i); } return groundIdx; }
TEST(SplitterTest, test_tile_filter) { StageFactory f; // create the reader Options ops1; ops1.add("filename", Support::datapath("las/1.2-with-color.las")); LasReader r; r.setOptions(ops1); Options o; Option length("length", 1000); o.add(length); // create the tile filter and prepare SplitterFilter s; s.setOptions(o); s.setInput(r); PointTable table; s.prepare(table); PointViewSet viewSet = s.execute(table); std::vector<PointViewPtr> views; std::map<PointViewPtr, BOX2D> bounds; for (auto it = viewSet.begin(); it != viewSet.end(); ++it) { BOX2D b; PointViewPtr v = *it; v->calculateBounds(b); EXPECT_TRUE(b.maxx - b.minx <= 1000); EXPECT_TRUE(b.maxy - b.miny <= 1000); for (auto& p : bounds) EXPECT_FALSE(p.second.overlaps(b)); bounds[v] = b; views.push_back(v); } auto sorter = [&bounds](PointViewPtr p1, PointViewPtr p2) { BOX2D b1 = bounds[p1]; BOX2D b2 = bounds[p2]; return b1.minx < b2.minx ? true : b1.minx > b2.minx ? false : b1.miny < b2.miny; }; std::sort(views.begin(), views.end(), sorter); EXPECT_EQ(views.size(), 24u); size_t counts[] = {24, 25, 2, 26, 27, 10, 82, 68, 43, 57, 7, 71, 73, 61, 33, 84, 74, 4, 59, 70, 67, 34, 60, 4 }; for (size_t i = 0; i < views.size(); ++i) { PointViewPtr view = views[i]; EXPECT_EQ(view->size(), counts[i]); } }
PointViewSet StatisticalOutlierFilter::run(PointViewPtr input) { bool logOutput = log()->getLevel() > LogLevel::Debug1; if (logOutput) log()->floatPrecision(8); log()->get(LogLevel::Debug2) << "Process StatisticalOutlierFilter...\n"; // convert PointView to PointXYZ typedef pcl::PointCloud<pcl::PointXYZ> Cloud; Cloud::Ptr cloud(new Cloud); BOX3D bounds; input->calculateBounds(bounds); pclsupport::PDALtoPCD(input, *cloud, bounds); // PCL should provide console output at similar verbosity level as PDAL int level = log()->getLevel(); switch (level) { case 0: pcl::console::setVerbosityLevel(pcl::console::L_ALWAYS); break; case 1: pcl::console::setVerbosityLevel(pcl::console::L_ERROR); break; case 2: pcl::console::setVerbosityLevel(pcl::console::L_WARN); break; case 3: pcl::console::setVerbosityLevel(pcl::console::L_INFO); break; case 4: pcl::console::setVerbosityLevel(pcl::console::L_DEBUG); break; default: pcl::console::setVerbosityLevel(pcl::console::L_VERBOSE); break; } // setup the outlier filter pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor(true); sor.setInputCloud(cloud); sor.setMeanK(m_meanK); sor.setStddevMulThresh(m_multiplier); pcl::PointCloud<pcl::PointXYZ> output; sor.setNegative(true); sor.filter(output); // filtered to return inliers pcl::PointIndicesPtr inliers(new pcl::PointIndices); sor.getRemovedIndices(*inliers); log()->get(LogLevel::Debug2) << inliers->indices.size() << std::endl; PointViewSet viewSet; if (inliers->indices.empty()) { log()->get(LogLevel::Warning) << "Requested filter would remove all points. Try increasing the multiplier.\n"; viewSet.insert(input); return viewSet; } // inverse are the outliers std::vector<int> outliers(input->size()-inliers->indices.size()); for (PointId i = 0, j = 0, k = 0; i < input->size(); ++i) { if (i == (PointId)inliers->indices[j]) { j++; continue; } outliers[k++] = i; } if (!outliers.empty() && (m_classify || m_extract)) { if (m_classify) { log()->get(LogLevel::Debug2) << "Labeled " << outliers.size() << " outliers as noise!\n"; // set the classification label of outlier returns as 18 // (corresponding to ASPRS LAS specification for high noise) for (const auto& i : outliers) { input->setField(Dimension::Id::Classification, i, 18); } viewSet.insert(input); } if (m_extract) { log()->get(LogLevel::Debug2) << "Extracted " << inliers->indices.size() << " inliers!\n"; // create new PointView containing only outliers PointViewPtr output = input->makeNew(); for (const auto& i : inliers->indices) { output->appendPoint(*input, i); } viewSet.erase(input); viewSet.insert(output); } } else { if (outliers.empty()) log()->get(LogLevel::Warning) << "Filtered cloud has no outliers!\n"; if (!(m_classify || m_extract)) log()->get(LogLevel::Warning) << "Must choose --classify or --extract\n"; // return the input buffer unchanged viewSet.insert(input); } return viewSet; }
void NitfWriter::write(const PointViewPtr view) { m_bounds.grow(view->calculateBounds(true)); LasWriter::write(view); }
PointViewSet DartSampleFilter::run(PointViewPtr input) { PointViewSet viewSet; PointViewPtr output = input->makeNew(); log()->floatPrecision(2); log()->get(LogLevel::Info) << "DartSampleFilter (radius=" << m_radius << ")\n"; BOX3D buffer_bounds; input->calculateBounds(buffer_bounds); typedef pcl::PointCloud<pcl::PointXYZ> Cloud; Cloud::Ptr cloud(new Cloud); pclsupport::PDALtoPCD(input, *cloud, buffer_bounds); int level = log()->getLevel(); switch (level) { case 0: pcl::console::setVerbosityLevel(pcl::console::L_ALWAYS); break; case 1: pcl::console::setVerbosityLevel(pcl::console::L_ERROR); break; case 2: pcl::console::setVerbosityLevel(pcl::console::L_WARN); break; case 3: pcl::console::setVerbosityLevel(pcl::console::L_INFO); break; case 4: pcl::console::setVerbosityLevel(pcl::console::L_DEBUG); break; default: pcl::console::setVerbosityLevel(pcl::console::L_VERBOSE); break; } pcl::DartSample<pcl::PointXYZ> ds; ds.setInputCloud(cloud); ds.setRadius(m_radius); std::vector<int> samples; ds.filter(samples); if (samples.empty()) { log()->get(LogLevel::Warning) << "Filtered cloud has no points!\n"; return viewSet; } for (const auto& i : samples) output->appendPoint(*input, i); double frac = (double)samples.size() / (double)cloud->size(); log()->get(LogLevel::Info) << "Retaining " << samples.size() << " of " << cloud->size() << " points (" << 100*frac << "%)\n"; viewSet.insert(output); return viewSet; }
void NitfWriter::writeView(const PointViewPtr view) { view->calculateBounds(m_bounds); LasWriter::writeView(view); }
PointViewSet PoissonFilter::run(PointViewPtr input) { PointViewPtr output = input->makeNew(); PointViewSet viewSet; viewSet.insert(output); bool logOutput = log()->getLevel() > LogLevel::Debug1; if (logOutput) log()->floatPrecision(8); log()->get(LogLevel::Debug2) << "Process PoissonFilter..." << std::endl; BOX3D buffer_bounds; input->calculateBounds(buffer_bounds); // convert PointView to PointNormal typedef pcl::PointCloud<pcl::PointXYZ> Cloud; Cloud::Ptr cloud(new Cloud); pclsupport::PDALtoPCD(input, *cloud, buffer_bounds); pclsupport::setLogLevel(log()->getLevel()); // pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointNormal>); pcl::search::KdTree<pcl::PointXYZ>::Ptr tree; pcl::search::KdTree<pcl::PointNormal>::Ptr tree2; // Create search tree tree.reset(new pcl::search::KdTree<pcl::PointXYZ> (false)); tree->setInputCloud(cloud); // Normal estimation pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n; pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal> ()); n.setInputCloud(cloud); n.setSearchMethod(tree); n.setKSearch(20); n.compute(*normals); // Concatenate XYZ and normal information pcl::concatenateFields(*cloud, *normals, *cloud_with_normals); // Create search tree tree2.reset(new pcl::search::KdTree<pcl::PointNormal>); tree2->setInputCloud(cloud_with_normals); // initial setup pcl::Poisson<pcl::PointNormal> p; p.setInputCloud(cloud_with_normals); p.setSearchMethod(tree2); p.setDepth(m_depth); p.setPointWeight(m_point_weight); // create PointCloud for results pcl::PolygonMesh grid; p.reconstruct(grid); Cloud::Ptr cloud_f(new Cloud); pcl::fromPCLPointCloud2(grid.cloud, *cloud_f); if (cloud_f->points.empty()) { log()->get(LogLevel::Debug2) << "Filtered cloud has no points!" << std::endl; return viewSet; } pclsupport::PCDtoPDAL(*cloud_f, output, buffer_bounds); log()->get(LogLevel::Debug2) << cloud->points.size() << " before, " << cloud_f->points.size() << " after" << std::endl; log()->get(LogLevel::Debug2) << output->size() << std::endl; return viewSet; }