Exemple #1
0
// 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());
        }
    }
}
Exemple #2
0
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);
}
Exemple #3
0
void PointView::calculateBounds(const PointViewSet& set, BOX3D& output)
{
    for (auto iter = set.begin(); iter != set.end(); ++iter)
    {
        PointViewPtr buf = *iter;
        buf->calculateBounds(output);
    }
}
Exemple #4
0
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);
}
Exemple #5
0
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);
}
Exemple #6
0
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++]);
}
Exemple #7
0
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);
}
Exemple #8
0
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;
}
Exemple #10
0
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;
}
Exemple #11
0
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;
}
Exemple #12
0
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;
}
Exemple #14
0
void NitfWriter::write(const PointViewPtr view)
{
    m_bounds.grow(view->calculateBounds(true));

    LasWriter::write(view);
}
Exemple #15
0
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;
}
Exemple #16
0
void NitfWriter::writeView(const PointViewPtr view)
{
    view->calculateBounds(m_bounds);
    LasWriter::writeView(view);
}
Exemple #17
0
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;
}