PointViewSet LocateFilter::run(PointViewPtr inView) { PointViewSet viewSet; if (!inView->size()) return viewSet; PointId minidx, maxidx; double minval = (std::numeric_limits<double>::max)(); double maxval = std::numeric_limits<double>::lowest(); for (PointId idx = 0; idx < inView->size(); idx++) { double val = inView->getFieldAs<double>(m_dimId, idx); if (val > maxval) { maxval = val; maxidx = idx; } if (val < minval) { minval = val; minidx = idx; } } PointViewPtr outView = inView->makeNew(); if (Utils::iequals("min", m_minmax)) outView->appendPoint(*inView.get(), minidx); if (Utils::iequals("max", m_minmax)) outView->appendPoint(*inView.get(), maxidx); viewSet.insert(outView); return viewSet; }
void ignoreDimRange(DimRange dr, PointViewPtr input, PointViewPtr keep, PointViewPtr ignore) { PointRef point(*input, 0); for (PointId i = 0; i < input->size(); ++i) { point.setPointId(i); if (dr.valuePasses(point.getFieldAs<double>(dr.m_id))) ignore->appendPoint(*input, i); else keep->appendPoint(*input, i); } }
void segmentLastReturns(PointViewPtr input, PointViewPtr last, PointViewPtr other) { PointRef point(*input, 0); for (PointId i = 0; i < input->size(); ++i) { point.setPointId(i); if (point.getFieldAs<uint8_t>(Dimension::Id::ReturnNumber) == point.getFieldAs<uint8_t>(Dimension::Id::NumberOfReturns)) last->appendPoint(*input, i); else other->appendPoint(*input, i); } }
PointViewSet PredicateFilter::run(PointViewPtr view) { MetadataNode n; m_pythonMethod->resetArguments(); m_pythonMethod->begin(*view, n); m_pythonMethod->execute(); if (!m_pythonMethod->hasOutputVariable("Mask")) throw pdal::pdal_error("Mask variable not set in predicate " "filter function."); PointViewPtr outview = view->makeNew(); void *pydata = m_pythonMethod->extractResult("Mask", Dimension::Type::Unsigned8); char *ok = (char *)pydata; for (PointId idx = 0; idx < view->size(); ++idx) if (*ok++) outview->appendPoint(*view, idx); PointViewSet viewSet; viewSet.insert(outview); return viewSet; }
void HAGFilter::filter(PointView& view) { PointViewPtr gView = view.makeNew(); PointViewPtr ngView = view.makeNew(); std::vector<PointId> gIdx, ngIdx; // First pass: Separate into ground and non-ground views. for (PointId i = 0; i < view.size(); ++i) { double c = view.getFieldAs<double>(Dimension::Id::Classification, i); if (c == 2) { gView->appendPoint(view, i); gIdx.push_back(i); } else { ngView->appendPoint(view, i); ngIdx.push_back(i); } } // Bail if there weren't any points classified as ground. if (gView->size() == 0) throwError("Input PointView does not have any points classified " "as ground"); // Build the 2D KD-tree. KD2Index kdi(*gView); kdi.build(); // Second pass: Find Z difference between non-ground points and the nearest // neighbor (2D) in the ground view. for (PointId i = 0; i < ngView->size(); ++i) { PointRef point = ngView->point(i); double z0 = point.getFieldAs<double>(Dimension::Id::Z); auto ids = kdi.neighbors(point, 1); double z1 = gView->getFieldAs<double>(Dimension::Id::Z, ids[0]); view.setField(Dimension::Id::HeightAboveGround, ngIdx[i], z0 - z1); } // Final pass: Ensure that all ground points have height value pegged at 0. for (auto const& i : gIdx) view.setField(Dimension::Id::HeightAboveGround, i, 0.0); }
MetadataNode InfoKernel::dumpQuery(PointViewPtr inView) const { int count; std::string location; // See if there's a provided point count. StringList parts = Utils::split2(m_queryPoint, '/'); if (parts.size() == 2) { location = parts[0]; count = atoi(parts[1].c_str()); } else if (parts.size() == 1) { location = parts[0]; count = inView->size(); } else count = 0; if (count == 0) throw pdal_error("Invalid location specificiation. " "--query=\"X,Y[/count]\""); auto seps = [](char c){ return (c == ',' || c == '|' || c == ' '); }; std::vector<std::string> tokens = Utils::split2(location, seps); std::vector<double> values; for (auto ti = tokens.begin(); ti != tokens.end(); ++ti) { double d; if (Utils::fromString(*ti, d)) values.push_back(d); } if (values.size() != 2 && values.size() != 3) throw pdal_error("--points must be two or three values"); PointViewPtr outView = inView->makeNew(); std::vector<PointId> ids; if (values.size() >= 3) { KD3Index kdi(*inView); kdi.build(); ids = kdi.neighbors(values[0], values[1], values[2], count); } else { KD2Index kdi(*inView); kdi.build(); ids = kdi.neighbors(values[0], values[1], count); } for (auto i = ids.begin(); i != ids.end(); ++i) outView->appendPoint(*inView.get(), *i); return outView->toMetadata(); }
PointViewSet PMFFilter::run(PointViewPtr input) { bool logOutput = log()->getLevel() > LogLevel::Debug1; if (logOutput) log()->floatPrecision(8); log()->get(LogLevel::Debug2) << "Process PMFFilter...\n"; auto idx = processGround(input); PointViewSet viewSet; if (!idx.empty() && (m_classify || m_extract)) { if (m_classify) { log()->get(LogLevel::Debug2) << "Labeled " << idx.size() << " ground returns!\n"; // set the classification label of ground returns as 2 // (corresponding to ASPRS LAS specification) for (const auto& i : idx) { input->setField(Dimension::Id::Classification, i, 2); } viewSet.insert(input); } if (m_extract) { log()->get(LogLevel::Debug2) << "Extracted " << idx.size() << " ground returns!\n"; // create new PointView containing only ground returns PointViewPtr output = input->makeNew(); for (const auto& i : idx) { output->appendPoint(*input, i); } viewSet.erase(input); viewSet.insert(output); } } else { if (idx.empty()) log()->get(LogLevel::Debug2) << "Filtered cloud has no ground returns!\n"; if (!(m_classify || m_extract)) log()->get(LogLevel::Debug2) << "Must choose --classify or --extract\n"; // return the input buffer unchanged viewSet.insert(input); } return viewSet; }
point_count_t RxpPointcloud::writeSavedPoints(PointViewPtr view, point_count_t count) { point_count_t numRead = 0; while (m_idx < m_view->size() && numRead < count) { view->appendPoint(*m_view, m_idx); ++m_idx, ++numRead; } return numRead; };
PointViewSet SMRFilter::run(PointViewPtr view) { log()->get(LogLevel::Info) << "run: Process SMRFilter...\n"; std::vector<PointId> idx = processGround(view); PointViewSet viewSet; if (!idx.empty() && (m_classify || m_extract)) { if (m_classify) { log()->get(LogLevel::Info) << "run: Labeled " << idx.size() << " ground returns!\n"; // set the classification label of ground returns as 2 // (corresponding to ASPRS LAS specification) for (const auto& i : idx) { view->setField(Dimension::Id::Classification, i, 2); } viewSet.insert(view); } if (m_extract) { log()->get(LogLevel::Info) << "run: Extracted " << idx.size() << " ground returns!\n"; // create new PointView containing only ground returns PointViewPtr output = view->makeNew(); for (const auto& i : idx) { output->appendPoint(*view, i); } viewSet.erase(view); viewSet.insert(output); } } else { if (idx.empty()) log()->get(LogLevel::Info) << "run: Filtered cloud has no ground returns!\n"; if (!(m_classify || m_extract)) log()->get(LogLevel::Info) << "run: Must choose --classify or --extract\n"; // return the view buffer unchanged viewSet.insert(view); } return viewSet; }
PointViewSet run(PointViewPtr view) { if (m_count > view->size()) log()->get(LogLevel::Warning) << "Requested number of points (count=" << m_count << ") exceeds number of available points.\n"; PointViewSet viewSet; PointViewPtr outView = view->makeNew(); for (PointId i = view->size() - std::min(m_count, view->size()); i < view->size(); ++i) outView->appendPoint(*view, i); viewSet.insert(outView); return viewSet; }
virtual PointViewSet run(PointViewPtr view) { PointViewSet out; PointViewPtr v = view->makeNew(); for (PointId i = 0; i < view->size(); ++i) { if (i && (i % m_viewSize == 0)) { out.insert(v); v = v->makeNew(); } v->appendPoint(*view, i); } out.insert(v); return out; }
PointViewSet SampleFilter::run(PointViewPtr inView) { point_count_t np = inView->size(); // Return empty PointViewSet if the input PointView has no points. // Otherwise, make a new output PointView. PointViewSet viewSet; if (!np) return viewSet; PointViewPtr outView = inView->makeNew(); // Build the 3D KD-tree. KD3Index index(*inView); index.build(); // All points are marked as kept (1) by default. As they are masked by // neighbors within the user-specified radius, their value is changed to 0. std::vector<int> keep(np, 1); // We are able to subsample in a single pass over the shuffled indices. for (PointId i = 0; i < np; ++i) { // If a point is masked, it is forever masked, and cannot be part of the // sampled cloud. Otherwise, the current index is appended to the output // PointView. if (keep[i] == 0) continue; outView->appendPoint(*inView, i); // We now proceed to mask all neighbors within m_radius of the kept // point. auto ids = index.radius(i, m_radius); for (auto const& id : ids) keep[id] = 0; } // Simply calculate the percentage of retained points. double frac = (double)outView->size() / (double)inView->size(); log()->get(LogLevel::Debug2) << "Retaining " << outView->size() << " of " << inView->size() << " points (" << 100 * frac << "%)\n"; viewSet.insert(outView); return viewSet; }
PointViewSet RangeFilter::run(PointViewPtr inView) { PointViewSet viewSet; if (!inView->size()) return viewSet; PointViewPtr outView = inView->makeNew(); for (PointId i = 0; i < inView->size(); ++i) { PointRef point = inView->point(i); if (processOne(point)) outView->appendPoint(*inView, i); } viewSet.insert(outView); return viewSet; }
PointViewSet MADFilter::run(PointViewPtr view) { using namespace Dimension; PointViewSet viewSet; PointViewPtr output = view->makeNew(); auto estimate_median = [](std::vector<double> vals) { std::nth_element(vals.begin(), vals.begin()+vals.size()/2, vals.end()); return *(vals.begin()+vals.size()/2); }; std::vector<double> z(view->size()); for (PointId j = 0; j < view->size(); ++j) z[j] = view->getFieldAs<double>(m_dimId, j); double median = estimate_median(z); log()->get(LogLevel::Debug) << getName() << " estimated median value: " << median << std::endl; std::transform(z.begin(), z.end(), z.begin(), [median](double v) { return std::fabs(v - median); }); double mad = estimate_median(z)*m_madMultiplier; log()->get(LogLevel::Debug) << getName() << " mad " << mad << std::endl; for (PointId j = 0; j < view->size(); ++j) { if (z[j]/mad < m_multiplier) output->appendPoint(*view, j); } double low_fence = median - m_multiplier * mad; double hi_fence = median + m_multiplier * mad; log()->get(LogLevel::Debug) << getName() << " cropping " << m_dimName << " in the range (" << low_fence << "," << hi_fence << ")" << std::endl; viewSet.erase(view); viewSet.insert(output); return viewSet; }
MetadataNode InfoKernel::dumpPoints(PointViewPtr inView) const { MetadataNode root; PointViewPtr outView = inView->makeNew(); // Stick points in a inViewfer. std::vector<PointId> points = getListOfPoints(m_pointIndexes); for (size_t i = 0; i < points.size(); ++i) { PointId id = (PointId)points[i]; if (id < inView->size()) outView->appendPoint(*inView.get(), id); } MetadataNode tree = outView->toMetadata(); std::string prefix("point "); for (size_t i = 0; i < outView->size(); ++i) { MetadataNode n = tree.findChild(std::to_string(i)); n.add("PointId", points[i]); root.add(n.clone("point")); } return root; }
void RialtoReader::doQuery(const TileMath& tmm, const GpkgTile& tile, PointViewPtr view, double qMinX, double qMinY, double qMaxX, double qMaxY) { const uint32_t level = tile.getLevel(); const uint32_t column = tile.getColumn(); const uint32_t row = tile.getRow(); const uint32_t numPoints = tile.getNumPoints(); // if this tile is entirely inside the query box, then // we won't need to check each point double tileMinX, tileMinY, tileMaxX, tileMaxY; tmm.getTileBounds(column, row, level, tileMinX, tileMinY, tileMaxX, tileMaxY); const bool tileEntirelyInsideQueryBox = tmm.rectContainsRect(qMinX, qMinY, qMaxX, qMaxY, tileMinX, tileMinY, tileMaxX, tileMaxY); log()->get(LogLevel::Debug) << " intersecting tile " << "(" << level << "," << column << "," << row << ")" << " contains " << numPoints << " points" << std::endl; PointViewPtr tempView = view->makeNew(); GpkgTile::exportToPV(numPoints, tempView, tile.getBlob()); for (uint32_t i=0; i<tempView->size(); i++) { const double x = tempView->getFieldAs<double>(Dimension::Id::X, i); const double y = tempView->getFieldAs<double>(Dimension::Id::Y, i); if (tileEntirelyInsideQueryBox || (x >= qMinX && x <= qMaxX && y >= qMinY && y <= qMaxY)) { view->appendPoint(*tempView, i); } } }
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::processGround(PointViewPtr view) { // 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); // Progressively filter ground returns using morphological open for (size_t j = 0; j < wsvec.size(); ++j) { // Limit filtering to those points currently considered ground returns PointViewPtr ground = view->makeNew(); for (PointId i = 0; i < groundIdx.size(); ++i) ground->appendPoint(*view, groundIdx[i]); log()->get(LogLevel::Debug) << "Iteration " << j << " (height threshold = " << htvec[j] << ", window size = " << wsvec[j] << ")...\n"; // Create new cloud to hold the filtered results. Apply the // morphological opening operation at the current window size. auto maxZ = morphOpen(ground, wsvec[j]*0.5); // Find indices of the points whose difference between the source and // filtered point clouds is less than the current height threshold. std::vector<PointId> groundNewIdx; for (PointId i = 0; i < ground->size(); ++i) { double z0 = ground->getFieldAs<double>(Dimension::Id::Z, i); float diff = z0 - maxZ[i]; if (diff < htvec[j]) groundNewIdx.push_back(groundIdx[i]); } groundIdx.swap(groundNewIdx); log()->get(LogLevel::Debug) << "Ground now has " << groundIdx.size() << " points.\n"; } return groundIdx; }
std::vector<PointId> PMFFilter::processGround(PointViewPtr view) { point_count_t np(view->size()); // Compute the series of window sizes and height thresholds std::vector<float> height_thresholds; std::vector<float> window_sizes; int iteration = 0; float window_size = 0.0f; float height_threshold = 0.0f; while (window_size < m_maxWindowSize) { // Determine the initial window size. if (1) // exponential window_size = m_cellSize * (2.0f * std::pow(2, iteration) + 1.0f); else window_size = m_cellSize * (2.0f * (iteration+1) * 2 + 1.0f); // Calculate the height threshold to be used in the next iteration. if (iteration == 0) height_threshold = m_initialDistance; else height_threshold = m_slope * (window_size - window_sizes[iteration-1]) * m_cellSize + m_initialDistance; // Enforce max distance on height threshold if (height_threshold > m_maxDistance) height_threshold = m_maxDistance; window_sizes.push_back(window_size); height_thresholds.push_back(height_threshold); iteration++; } std::vector<PointId> groundIdx; for (PointId i = 0; i < np; ++i) groundIdx.push_back(i); // Progressively filter ground returns using morphological open for (size_t j = 0; j < window_sizes.size(); ++j) { // Limit filtering to those points currently considered ground returns PointViewPtr ground = view->makeNew(); for (PointId i = 0; i < groundIdx.size(); ++i) ground->appendPoint(*view, groundIdx[i]); printf(" Iteration %ld (height threshold = %f, window size = %f)...", j, height_thresholds[j], window_sizes[j]); // Create new cloud to hold the filtered results. Apply the morphological // opening operation at the current window size. auto maxZ = morphOpen(ground, window_sizes[j]*0.5); // Find indices of the points whose difference between the source and // filtered point clouds is less than the current height threshold. std::vector<PointId> pt_indices; for (PointId i = 0; i < ground->size(); ++i) { double z0 = ground->getFieldAs<double>(Dimension::Id::Z, i); double z1 = maxZ[i]; float diff = z0 - z1; if (diff < height_thresholds[j]) pt_indices.push_back(groundIdx[i]); } groundIdx.swap(pt_indices); } return groundIdx; }
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; }
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; }