bool cc2Point5DimEditor::RasterGrid::fillWith( ccGenericPointCloud* cloud, unsigned char projectionDimension, cc2Point5DimEditor::ProjectionType projectionType, bool interpolateEmptyCells, cc2Point5DimEditor::ProjectionType sfInterpolation/*=INVALID_PROJECTION_TYPE*/, ccProgressDialog* progressDialog/*=0*/) { if (!cloud) { assert(false); return false; } //current parameters unsigned gridTotalSize = width * height; //vertical dimension const unsigned char Z = projectionDimension; assert(Z >= 0 && Z <= 2); const unsigned char X = Z == 2 ? 0 : Z +1; const unsigned char Y = X == 2 ? 0 : X +1; //do we need to interpolate scalar fields? ccPointCloud* pc = cloud->isA(CC_TYPES::POINT_CLOUD) ? static_cast<ccPointCloud*>(cloud) : 0; bool interpolateSF = (sfInterpolation != INVALID_PROJECTION_TYPE); interpolateSF &= (pc && pc->hasScalarFields()); if (interpolateSF) { unsigned sfCount = pc->getNumberOfScalarFields(); bool memoryError = false; size_t previousCount = scalarFields.size(); if (sfCount > previousCount) { try { scalarFields.resize(sfCount,0); } catch (const std::bad_alloc&) { //not enough memory memoryError = true; } } for (size_t i=previousCount; i<sfCount; ++i) { assert(scalarFields[i] == 0); scalarFields[i] = new double[gridTotalSize]; if (!scalarFields[i]) { //not enough memory memoryError = true; break; } } if (memoryError) { ccLog::Warning(QString("[Rasterize] Failed to allocate memory for scalar fields!")); } } //filling the grid unsigned pointCount = cloud->size(); double gridMaxX = gridStep * width; double gridMaxY = gridStep * height; if (progressDialog) { progressDialog->setMethodTitle("Grid generation"); progressDialog->setInfo(qPrintable(QString("Points: %1\nCells: %2 x %3").arg(pointCount).arg(width).arg(height))); progressDialog->start(); progressDialog->show(); QCoreApplication::processEvents(); } CCLib::NormalizedProgress nProgress(progressDialog,pointCount); for (unsigned n=0; n<pointCount; ++n) { const CCVector3* P = cloud->getPoint(n); CCVector3d relativePos = CCVector3d::fromArray(P->u) - minCorner; int i = static_cast<int>(relativePos.u[X]/gridStep); int j = static_cast<int>(relativePos.u[Y]/gridStep); //specific case: if we fall exactly on the max corner of the grid box if (i == static_cast<int>(width) && relativePos.u[X] == gridMaxX) --i; if (j == static_cast<int>(height) && relativePos.u[Y] == gridMaxY) --j; //we skip points outside the box! if ( i < 0 || i >= static_cast<int>(width) || j < 0 || j >= static_cast<int>(height) ) continue; assert(i >= 0 && j >= 0); RasterCell* aCell = data[j]+i; unsigned& pointsInCell = aCell->nbPoints; if (pointsInCell) { if (P->u[Z] < aCell->minHeight) { aCell->minHeight = P->u[Z]; if (projectionType == PROJ_MINIMUM_VALUE) aCell->pointIndex = n; } else if (P->u[Z] > aCell->maxHeight) { aCell->maxHeight = P->u[Z]; if (projectionType == PROJ_MAXIMUM_VALUE) aCell->pointIndex = n; } } else { aCell->minHeight = aCell->maxHeight = P->u[Z]; aCell->pointIndex = n; } // Sum the points heights aCell->avgHeight += P->u[Z]; aCell->stdDevHeight += static_cast<double>(P->u[Z])*P->u[Z]; //scalar fields if (interpolateSF) { int pos = j*static_cast<int>(width)+i; //pos in 2D SF grid(s) assert(pos < static_cast<int>(gridTotalSize)); for (size_t k=0; k<scalarFields.size(); ++k) { if (scalarFields[k]) { CCLib::ScalarField* sf = pc->getScalarField(static_cast<unsigned>(k)); assert(sf); ScalarType sfValue = sf->getValue(n); ScalarType formerValue = static_cast<ScalarType>(scalarFields[k][pos]); if (pointsInCell && ccScalarField::ValidValue(formerValue)) { if (ccScalarField::ValidValue(sfValue)) { switch (sfInterpolation) { case PROJ_MINIMUM_VALUE: // keep the minimum value scalarFields[k][pos] = std::min<double>(formerValue,sfValue); break; case PROJ_AVERAGE_VALUE: //we sum all values (we will divide them later) scalarFields[k][pos] += sfValue; break; case PROJ_MAXIMUM_VALUE: // keep the maximum value scalarFields[k][pos] = std::max<double>(formerValue,sfValue); break; default: assert(false); break; } } } else { //for the first (vaild) point, we simply have to store its SF value (in any case) scalarFields[k][pos] = sfValue; } } } } pointsInCell++; if (!nProgress.oneStep()) { //process cancelled by user return false; } } //update SF grids for 'average' cases if (sfInterpolation == PROJ_AVERAGE_VALUE) { for (size_t k=0; k<scalarFields.size(); ++k) { if (scalarFields[k]) { double* _gridSF = scalarFields[k]; for (unsigned j=0;j<height;++j) { RasterCell* cell = data[j]; for (unsigned i=0; i<width; ++i,++cell,++_gridSF) { if (cell->nbPoints > 1) { ScalarType s = static_cast<ScalarType>(*_gridSF); if (ccScalarField::ValidValue(s)) //valid SF value { *_gridSF /= cell->nbPoints; } } } } } } } //update the main grid (average height and std.dev. computation + current 'height' value) { for (unsigned j=0; j<height; ++j) { RasterCell* cell = data[j]; for (unsigned i=0; i<width; ++i,++cell) { if (cell->nbPoints > 1) { cell->avgHeight /= cell->nbPoints; cell->stdDevHeight = sqrt(fabs(cell->stdDevHeight/cell->nbPoints - cell->avgHeight*cell->avgHeight)); } else { cell->stdDevHeight = 0; } if (cell->nbPoints != 0) { //set the right 'height' value switch (projectionType) { case PROJ_MINIMUM_VALUE: cell->h = cell->minHeight; break; case PROJ_AVERAGE_VALUE: cell->h = cell->avgHeight; break; case PROJ_MAXIMUM_VALUE: cell->h = cell->maxHeight; break; default: assert(false); break; } } } } } //compute the number of non empty cells nonEmptyCellCount = 0; { for (unsigned i=0; i<height; ++i) for (unsigned j=0; j<width; ++j) if (data[i][j].nbPoints) ++nonEmptyCellCount; } //specific case: interpolate the empty cells if (interpolateEmptyCells) { std::vector<CCVector2> the2DPoints; if (nonEmptyCellCount < 3) { ccLog::Warning("[Rasterize] Not enough non-empty cells to interpolate!"); } else if (nonEmptyCellCount < width * height) //otherwise it's useless! { try { the2DPoints.resize(nonEmptyCellCount); } catch (const std::bad_alloc&) { //out of memory ccLog::Warning("[Rasterize] Not enough memory to interpolate empty cells!"); } } //fill 2D vector with non-empty cell indexes if (!the2DPoints.empty()) { unsigned index = 0; for (unsigned j=0; j<height; ++j) { const RasterCell* cell = data[j]; for (unsigned i=0; i<width; ++i, ++cell) { if (cell->nbPoints) { //we only use the non-empty cells to interpolate the2DPoints[index++] = CCVector2(static_cast<PointCoordinateType>(i),static_cast<PointCoordinateType>(j)); } } } assert(index == nonEmptyCellCount); //mesh the '2D' points CCLib::Delaunay2dMesh delaunayMesh; char errorStr[1024]; if (delaunayMesh.buildMesh(the2DPoints,0,errorStr)) { unsigned triNum = delaunayMesh.size(); //now we are going to 'project' all triangles on the grid delaunayMesh.placeIteratorAtBegining(); for (unsigned k=0; k<triNum; ++k) { const CCLib::VerticesIndexes* tsi = delaunayMesh.getNextTriangleVertIndexes(); //get the triangle bounding box (in grid coordinates) int P[3][2]; int xMin = 0, yMin = 0, xMax = 0, yMax = 0; { for (unsigned j=0; j<3; ++j) { const CCVector2& P2D = the2DPoints[tsi->i[j]]; P[j][0] = static_cast<int>(P2D.x); P[j][1] = static_cast<int>(P2D.y); } xMin = std::min(std::min(P[0][0],P[1][0]),P[2][0]); yMin = std::min(std::min(P[0][1],P[1][1]),P[2][1]); xMax = std::max(std::max(P[0][0],P[1][0]),P[2][0]); yMax = std::max(std::max(P[0][1],P[1][1]),P[2][1]); } //now scan the cells { //pre-computation for barycentric coordinates const double& valA = data[ P[0][1] ][ P[0][0] ].h; const double& valB = data[ P[1][1] ][ P[1][0] ].h; const double& valC = data[ P[2][1] ][ P[2][0] ].h; int det = (P[1][1]-P[2][1])*(P[0][0]-P[2][0]) + (P[2][0]-P[1][0])*(P[0][1]-P[2][1]); for (int j=yMin; j<=yMax; ++j) { RasterCell* cell = data[static_cast<unsigned>(j)]; for (int i=xMin; i<=xMax; ++i) { //if the cell is empty if (!cell[i].nbPoints) { //we test if it's included or not in the current triangle //Point Inclusion in Polygon Test (inspired from W. Randolph Franklin - WRF) bool inside = false; for (int ti=0; ti<3; ++ti) { const int* P1 = P[ti]; const int* P2 = P[(ti+1)%3]; if ((P2[1] <= j &&j < P1[1]) || (P1[1] <= j && j < P2[1])) { int t = (i-P2[0])*(P1[1]-P2[1])-(P1[0]-P2[0])*(j-P2[1]); if (P1[1] < P2[1]) t = -t; if (t < 0) inside = !inside; } } //can we interpolate? if (inside) { double l1 = static_cast<double>((P[1][1]-P[2][1])*(i-P[2][0])+(P[2][0]-P[1][0])*(j-P[2][1]))/det; double l2 = static_cast<double>((P[2][1]-P[0][1])*(i-P[2][0])+(P[0][0]-P[2][0])*(j-P[2][1]))/det; double l3 = 1.0-l1-l2; cell[i].h = l1 * valA + l2 * valB + l3 * valC; assert(cell[i].h == cell[i].h); //interpolate SFs as well! for (size_t sfIndex=0; sfIndex<scalarFields.size(); ++sfIndex) { if (scalarFields[sfIndex]) { double* gridSF = scalarFields[sfIndex]; const double& sfValA = gridSF[ P[0][0] + P[0][1]*width ]; const double& sfValB = gridSF[ P[1][0] + P[1][1]*width ]; const double& sfValC = gridSF[ P[2][0] + P[2][1]*width ]; gridSF[i + j*width] = l1 * sfValA + l2 * sfValB + l3 * sfValC; } } } } } } } } } else { ccLog::Warning(QString("[Rasterize] Empty cells interpolation failed: Triangle lib. said '%1'").arg(errorStr)); } } } //computation of the average and extreme height values in the grid { minHeight = 0; maxHeight = 0; meanHeight = 0; validCellCount = 0; for (unsigned i=0; i<height; ++i) { for (unsigned j=0; j<width; ++j) { if (data[i][j].h == data[i][j].h) //valid height { double h = data[i][j].h; if (validCellCount) { if (h < minHeight) minHeight = h; else if (h > maxHeight) maxHeight = h; meanHeight += h; } else { //first valid cell meanHeight = minHeight = maxHeight = h; } ++validCellCount; } } } meanHeight /= validCellCount; } setValid(true); return true; }
bool StereogramWidget::init(double angularStep_deg, ccHObject* entity, double resolution_deg/*=2.0*/) { m_angularStep_deg = angularStep_deg; if (m_densityGrid) delete m_densityGrid; m_densityGrid = 0; if (!entity) return false; ccProgressDialog pDlg(true); pDlg.setMethodTitle("Stereogram"); pDlg.setInfo("Preparing polar display..."); pDlg.start(); QApplication::processEvents(); size_t count = 0; ccHObject::Container facets; ccPointCloud* cloud = 0; //a set of facets? if (entity->isA(CC_TYPES::HIERARCHY_OBJECT)) { entity->filterChildren(facets,true,CC_TYPES::FACET); count = facets.size(); } //or a cloud? else if (entity->isA(CC_TYPES::POINT_CLOUD)) { cloud = static_cast<ccPointCloud*>(entity); if (cloud->hasNormals()) count = cloud->size(); } if (!count) return false; //pDlg.setMaximum(static_cast<int>(count)); CCLib::NormalizedProgress nProgress(&pDlg,static_cast<unsigned>(count)); //create the density grid FacetDensityGrid* densityGrid = new FacetDensityGrid(); densityGrid->step_deg = resolution_deg; densityGrid->step_R = 0.02; //dip steps (dip in [0,90]) //densityGrid->dSteps = static_cast<unsigned>(ceil(90.0 / densityGrid->step_deg)); //R steps (R in [0,1]) densityGrid->rSteps = static_cast<unsigned>(ceil(1.0 / densityGrid->step_R)); //dip direction steps (dip dir. in [0,360]) densityGrid->ddSteps = static_cast<unsigned>(ceil(360.0 / densityGrid->step_deg)); unsigned cellCount = densityGrid->rSteps * densityGrid->ddSteps; densityGrid->grid = new double[cellCount]; if (densityGrid->grid) { memset(densityGrid->grid,0,sizeof(double)*cellCount); CCVector3d Nmean(0,0,0); double surfaceSum = 0.0; for (size_t i=0; i<count; ++i) { CCVector3 N; double weight = 1.0; if (cloud) { N = cloud->getPointNormal(static_cast<unsigned>(i)); } else { ccFacet* facet = static_cast<ccFacet*>(facets[i]); N = facet->getNormal(); weight = facet->getSurface(); } Nmean.x += static_cast<double>(N.x) * weight; Nmean.y += static_cast<double>(N.y) * weight; Nmean.z += static_cast<double>(N.z) * weight; surfaceSum += weight; PointCoordinateType dipDir = 0, dip = 0; ccNormalVectors::ConvertNormalToDipAndDipDir(N,dip,dipDir); //unsigned iDip = static_cast<unsigned>(floor(static_cast<double>(dip)/densityGrid->step_deg)); //if (iDip == densityGrid->dSteps) // iDip--; unsigned iDipDir = static_cast<unsigned>(floor(static_cast<double>(dipDir)/densityGrid->step_deg)); if (iDipDir == densityGrid->ddSteps) iDipDir--; double dip_rad = dip * CC_DEG_TO_RAD; double R = sin(dip_rad) / (1.0 + cos(dip_rad)); unsigned iR = static_cast<unsigned>(floor(static_cast<double>(R)/densityGrid->step_R)); if (iR == densityGrid->rSteps) iR--; densityGrid->grid[iR + iDipDir * densityGrid->rSteps] += weight; //pDlg.setValue(static_cast<int>(i)); if (!nProgress.oneStep()) { //process cancelled by user delete densityGrid; return false; } } if (surfaceSum > 0) { Nmean.normalize(); CCVector3 N(static_cast<PointCoordinateType>(Nmean.x), static_cast<PointCoordinateType>(Nmean.y), static_cast<PointCoordinateType>(Nmean.z)); PointCoordinateType dipDir = 0, dip = 0; ccNormalVectors::ConvertNormalToDipAndDipDir(N,dip,dipDir); m_meanDipDir_deg = static_cast<double>(dipDir); m_meanDip_deg = static_cast<double>(dip); //set same value for the "filter" center m_clickDipDir_deg = m_meanDipDir_deg; m_clickDip_deg = m_meanDip_deg; } //compute min and max density { //DGM: only supported on C++x11! //std::pair<double*,double*> minmax = std::minmax_element(densityGrid->grid,densityGrid->grid+cellCount); //densityGrid->minMaxDensity[0] = *minmax.first; //densityGrid->minMaxDensity[1] = *minmax.second; densityGrid->minMaxDensity[0] = densityGrid->grid[0]; densityGrid->minMaxDensity[1] = densityGrid->grid[0]; for (unsigned j=1; j<cellCount; ++j) { if (densityGrid->grid[j] < densityGrid->minMaxDensity[0]) densityGrid->minMaxDensity[0] = densityGrid->grid[j]; else if (densityGrid->grid[j] > densityGrid->minMaxDensity[1]) densityGrid->minMaxDensity[1] = densityGrid->grid[j]; } } //pDlg.hide(); pDlg.stop(); QApplication::processEvents(); } else { //not enough memory! delete densityGrid; densityGrid = 0; } //replace old grid by new one! (even in case of failure! See below) m_densityGrid = densityGrid; update(); return true; }
bool DistanceMapGenerationTool::ComputeRadialDist( ccPointCloud* cloud, ccPolyline* profile, bool storeRadiiAsSF/*=false*/, ccMainAppInterface* app/*=0*/) { //check input cloud and profile/polyline if (!cloud || !profile) { if (app) app->dispToConsole(QString("Internal error: invalid input parameters"),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } assert(cloud && profile); //number of vertices for the profile CCLib::GenericIndexedCloudPersist* vertices = profile->getAssociatedCloud(); unsigned vertexCount = vertices->size(); if (vertexCount < 2) { if (app) app->dispToConsole(QString("Invalid polyline (not enough vertices)"),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } //profile meta-data ProfileMetaData profileDesc; if (!GetPoylineMetaData(profile, profileDesc)) { if (app) app->dispToConsole(QString("Invalid polyline (bad or missing meta-data)"),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } //reserve a new scalar field (or take the old one if it already exists) int sfIdx = cloud->getScalarFieldIndexByName(RADIAL_DIST_SF_NAME); if (sfIdx < 0) sfIdx = cloud->addScalarField(RADIAL_DIST_SF_NAME); if (sfIdx < 0) { if (app) app->dispToConsole(QString("Failed to allocate a new scalar field for computing distances! Try to free some memory ..."),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } ccScalarField* sf = static_cast<ccScalarField*>(cloud->getScalarField(sfIdx)); unsigned pointCount = cloud->size(); sf->resize(pointCount); //should always be ok assert(sf); ccScalarField* radiiSf = 0; if (storeRadiiAsSF) { int sfIdxRadii = cloud->getScalarFieldIndexByName(RADII_SF_NAME); if (sfIdxRadii < 0) sfIdxRadii = cloud->addScalarField(RADII_SF_NAME); if (sfIdxRadii < 0) { if (app) app->dispToConsole(QString("Failed to allocate a new scalar field for storing radii! You should try to free some memory ..."),ccMainAppInterface::WRN_CONSOLE_MESSAGE); //return false; } else { radiiSf = static_cast<ccScalarField*>(cloud->getScalarField(sfIdxRadii)); radiiSf->resize(pointCount); //should always be ok } } bool success = true; //now compute the distance between the cloud and the (implicit) surface of revolution { ccGLMatrix cloudToSurface = profileDesc.computeProfileToSurfaceTrans(); //we deduce the horizontal dimensions from the revolution axis const unsigned char dim1 = static_cast<unsigned char>(profileDesc.revolDim < 2 ? profileDesc.revolDim+1 : 0); const unsigned char dim2 = (dim1 < 2 ? dim1+1 : 0); ccProgressDialog dlg(true, app ? app->getMainWindow() : 0); dlg.setMethodTitle("Cloud to profile radial distance"); dlg.setInfo(qPrintable(QString("Polyline: %1 vertices\nCloud: %2 points").arg(vertexCount).arg(pointCount))); dlg.start(); CCLib::NormalizedProgress nProgress(static_cast<CCLib::GenericProgressCallback*>(&dlg),pointCount); for (unsigned i=0; i<pointCount; ++i) { const CCVector3* P = cloud->getPoint(i); //relative point position CCVector3 Prel = cloudToSurface * (*P); //deduce point height and radius (i.e. in profile 2D coordinate system) double height = Prel.u[profileDesc.revolDim] - profileDesc.heightShift; //TODO FIXME: we assume the surface of revolution is smooth! double radius = sqrt(Prel.u[dim1]*Prel.u[dim1] + Prel.u[dim2]*Prel.u[dim2]); if (radiiSf) { ScalarType radiusVal = static_cast<ScalarType>(radius); radiiSf->setValue(i,radiusVal); } //search nearest "segment" in polyline ScalarType minDist = NAN_VALUE; for (unsigned j=1; j<vertexCount; ++j) { const CCVector3* A = vertices->getPoint(j-1); const CCVector3* B = vertices->getPoint(j); double alpha = (height - A->y)/(B->y - A->y); if (alpha >= 0.0 && alpha <= 1.0) { //we deduce the right radius by linear interpolation double radius_th = A->x + alpha * (B->x - A->x); double dist = radius - radius_th; //we look at the closest segment (if the polyline is concave!) if (!CCLib::ScalarField::ValidValue(minDist) || dist*dist < minDist*minDist) { minDist = static_cast<ScalarType>(dist); } } } sf->setValue(i,minDist); if (!nProgress.oneStep()) { //cancelled by user for (unsigned j=i; j<pointCount; ++j) sf->setValue(j,NAN_VALUE); success = false; break; } //TEST //*const_cast<CCVector3*>(P) = Prel; } //TEST //cloud->invalidateBoundingBox(); } sf->computeMinAndMax(); cloud->setCurrentDisplayedScalarField(sfIdx); cloud->showSF(true); return success; }
bool ccKdTreeForFacetExtraction::FuseCells( ccKdTree* kdTree, double maxError, CCLib::DistanceComputationTools::ERROR_MEASURES errorMeasure, double maxAngle_deg, PointCoordinateType overlapCoef/*=1*/, bool closestFirst/*=true*/, CCLib::GenericProgressCallback* progressCb/*=0*/) { if (!kdTree) return false; ccGenericPointCloud* associatedGenericCloud = kdTree->associatedGenericCloud(); if (!associatedGenericCloud || !associatedGenericCloud->isA(CC_TYPES::POINT_CLOUD) || maxError < 0.0) return false; //get leaves std::vector<ccKdTree::Leaf*> leaves; if (!kdTree->getLeaves(leaves) || leaves.empty()) return false; //progress notification CCLib::NormalizedProgress nProgress(progressCb, static_cast<unsigned>(leaves.size())); if (progressCb) { progressCb->update(0); if (progressCb->textCanBeEdited()) { progressCb->setMethodTitle("Fuse Kd-tree cells"); progressCb->setInfo(qPrintable(QString("Cells: %1\nMax error: %2").arg(leaves.size()).arg(maxError))); } progressCb->start(); } ccPointCloud* pc = static_cast<ccPointCloud*>(associatedGenericCloud); //sort cells based on their population size (we start by the biggest ones) SortAlgo(leaves.begin(), leaves.end(), DescendingLeafSizeComparison); //set all 'userData' to -1 (i.e. unfused cells) { for (size_t i=0; i<leaves.size(); ++i) { leaves[i]->userData = -1; //check by the way that the plane normal is unit! assert(static_cast<double>(fabs(CCVector3(leaves[i]->planeEq).norm2()) - 1.0) < 1.0e-6); } } // cosine of the max angle between fused 'planes' const double c_minCosNormAngle = cos(maxAngle_deg * CC_DEG_TO_RAD); //fuse all cells, starting from the ones with the best error const int unvisitedNeighborValue = -1; bool cancelled = false; int macroIndex = 1; //starts at 1 (0 is reserved for cells already above the max error) { for (size_t i=0; i<leaves.size(); ++i) { ccKdTree::Leaf* currentCell = leaves[i]; if (currentCell->error >= maxError) currentCell->userData = 0; //0 = special group for cells already above the user defined threshold! //already fused? if (currentCell->userData != -1) { if (progressCb && !nProgress.oneStep()) //process canceled by user { cancelled = true; break; } continue; } //we create a new "macro cell" index currentCell->userData = macroIndex++; //we init the current set of 'fused' points with the cell's points CCLib::ReferenceCloud* currentPointSet = currentCell->points; //get current fused set centroid and normal CCVector3 currentCentroid = *CCLib::Neighbourhood(currentPointSet).getGravityCenter(); CCVector3 currentNormal(currentCell->planeEq); //visited neighbors ccKdTree::LeafSet visitedNeighbors; //set of candidates std::list<Candidate> candidates; //we are going to iteratively look for neighbor cells that could be fused to this one ccKdTree::LeafVector cellsToTest; cellsToTest.push_back(currentCell); if (progressCb && !nProgress.oneStep()) //process canceled by user { cancelled = true; break; } while (!cellsToTest.empty() || !candidates.empty()) { //get all neighbors around the 'waiting' cell(s) if (!cellsToTest.empty()) { ccKdTree::LeafSet neighbors; while (!cellsToTest.empty()) { if (!kdTree->getNeighborLeaves(cellsToTest.back(), neighbors, &unvisitedNeighborValue)) //we only consider unvisited cells! { //an error occurred return false; } cellsToTest.pop_back(); } //add those (new) neighbors to the 'visitedNeighbors' set //and to the candidates set by the way if they are not yet there for (ccKdTree::LeafSet::iterator it=neighbors.begin(); it != neighbors.end(); ++it) { ccKdTree::Leaf* neighbor = *it; std::pair<ccKdTree::LeafSet::iterator,bool> ret = visitedNeighbors.insert(neighbor); //neighbour not already in the set? if (ret.second) { //we create the corresponding candidate try { candidates.push_back(Candidate(neighbor)); } catch (const std::bad_alloc&) { //not enough memory! ccLog::Warning("[ccKdTreeForFacetExtraction] Not enough memory!"); return false; } } } } //is there remaining candidates? if (!candidates.empty()) { //update the set of candidates if (closestFirst && candidates.size() > 1) { for (std::list<Candidate>::iterator it = candidates.begin(); it !=candidates.end(); ++it) it->dist = (it->centroid-currentCentroid).norm2(); //sort candidates by their distance candidates.sort(CandidateDistAscendingComparison); } //we will keep track of the best fused 'couple' at each pass std::list<Candidate>::iterator bestIt = candidates.end(); CCLib::ReferenceCloud* bestFused = 0; CCVector3 bestNormal(0,0,0); double bestError = -1.0; unsigned skipCount = 0; for (std::list<Candidate>::iterator it = candidates.begin(); it != candidates.end(); /*++it*/) { assert(it->leaf && it->leaf->points); assert(currentPointSet->getAssociatedCloud() == it->leaf->points->getAssociatedCloud()); //if the leaf orientation is too different if (fabs(CCVector3(it->leaf->planeEq).dot(currentNormal)) < c_minCosNormAngle) { it = candidates.erase(it); //++it; //++skipCount; continue; } //compute the minimum distance between the candidate centroid and the 'currentPointSet' PointCoordinateType minDistToMainSet = 0.0; { for (unsigned j=0; j<currentPointSet->size(); ++j) { const CCVector3* P = currentPointSet->getPoint(j); PointCoordinateType d2 = (*P-it->centroid).norm2(); if (d2 < minDistToMainSet || j == 0) minDistToMainSet = d2; } minDistToMainSet = sqrt(minDistToMainSet); } //if the leaf is too far if (it->radius < minDistToMainSet / overlapCoef) { ++it; ++skipCount; continue; } //fuse the main set with the current candidate CCLib::ReferenceCloud* fused = new CCLib::ReferenceCloud(*currentPointSet); if (!fused->add(*(it->leaf->points))) { //not enough memory! ccLog::Warning("[ccKdTreeForFacetExtraction] Not enough memory!"); delete fused; if (currentPointSet != currentCell->points) delete currentPointSet; return false; } //fit a plane and estimate the resulting error double error = -1.0; const PointCoordinateType* planeEquation = CCLib::Neighbourhood(fused).getLSPlane(); if (planeEquation) error = CCLib::DistanceComputationTools::ComputeCloud2PlaneDistance(fused, planeEquation, errorMeasure); if (error < 0.0 || error > maxError) { //candidate is rejected it = candidates.erase(it); } else { //otherwise we keep track of the best one! if (bestError < 0.0 || error < bestError) { bestIt = it; bestError = error; if (bestFused) delete bestFused; bestFused = fused; bestNormal = CCVector3(planeEquation); fused = 0; if (closestFirst) break; //if we have found a good candidate, we stop here (closest first ;) } ++it; } if (fused) { delete fused; fused = 0; } } //we have a (best) candidate for this pass? if (bestIt != candidates.end()) { assert(bestFused && bestError >= 0.0); if (currentPointSet != currentCell->points) delete currentPointSet; currentPointSet = bestFused; { //update infos CCLib::Neighbourhood N(currentPointSet); //currentCentroid = *N.getGravityCenter(); //if we update it, the search will naturally shift along one dimension! //currentNormal = bestNormal; //same thing here for normals } bestIt->leaf->userData = currentCell->userData; //bestIt->leaf->userData = macroIndex++; //FIXME TEST //we will test this cell's neighbors as well cellsToTest.push_back(bestIt->leaf); if (progressCb && !nProgress.oneStep()) //process canceled by user { //premature end! candidates.clear(); cellsToTest.clear(); cancelled = true; break; } QApplication::processEvents(); //we also remove it from the candidates list candidates.erase(bestIt); } if (skipCount == candidates.size() && cellsToTest.empty()) { //only far leaves remain... candidates.clear(); } } } //no more candidates or cells to test //end of the fusion process for the current leaf if (currentPointSet != currentCell->points) delete currentPointSet; currentPointSet = 0; if (cancelled) break; } } //convert fused indexes to SF if (!cancelled) { pc->enableScalarField(); for (size_t i=0; i<leaves.size(); ++i) { CCLib::ReferenceCloud* subset = leaves[i]->points; if (subset) { ScalarType scalar = (ScalarType)leaves[i]->userData; if (leaves[i]->userData <= 0) //for unfused cells, we create new individual groups scalar = static_cast<ScalarType>(macroIndex++); //scalar = NAN_VALUE; //FIXME TEST for (unsigned j=0; j<subset->size(); ++j) subset->setPointScalarValue(j,scalar); } } //pc->setCurrentDisplayedScalarField(sfIdx); } return !cancelled; }
bool ccVolumeCalcTool::ComputeVolume( ccRasterGrid& grid, ccGenericPointCloud* ground, ccGenericPointCloud* ceil, const ccBBox& gridBox, unsigned char vertDim, double gridStep, unsigned gridWidth, unsigned gridHeight, ccRasterGrid::ProjectionType projectionType, ccRasterGrid::EmptyCellFillOption emptyCellFillStrategy, ccVolumeCalcTool::ReportInfo& reportInfo, double groundHeight = std::numeric_limits<double>::quiet_NaN(), double ceilHeight = std::numeric_limits<double>::quiet_NaN(), QWidget* parentWidget/*=0*/) { if ( gridStep <= 1.0e-8 || gridWidth == 0 || gridHeight == 0 || vertDim > 2) { assert(false); ccLog::Warning("[Volume] Invalid input parameters"); return false; } if (!ground && !ceil) { assert(false); ccLog::Warning("[Volume] No valid input cloud"); return false; } if (!gridBox.isValid()) { ccLog::Warning("[Volume] Invalid bounding-box"); return false; } //grid size unsigned gridTotalSize = gridWidth * gridHeight; if (gridTotalSize == 1) { if (parentWidget && QMessageBox::question(parentWidget, "Unexpected grid size", "The generated grid will only have 1 cell! Do you want to proceed anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return false; } else if (gridTotalSize > 10000000) { if (parentWidget && QMessageBox::question(parentWidget, "Big grid size", "The generated grid will have more than 10.000.000 cells! Do you want to proceed anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return false; } //memory allocation CCVector3d minCorner = CCVector3d::fromArray(gridBox.minCorner().u); if (!grid.init(gridWidth, gridHeight, gridStep, minCorner)) { //not enough memory return SendError("Not enough memory", parentWidget); } //progress dialog QScopedPointer<ccProgressDialog> pDlg(0); if (parentWidget) { pDlg.reset(new ccProgressDialog(true, parentWidget)); } ccRasterGrid groundRaster; if (ground) { if (!groundRaster.init(gridWidth, gridHeight, gridStep, minCorner)) { //not enough memory return SendError("Not enough memory", parentWidget); } if (groundRaster.fillWith( ground, vertDim, projectionType, emptyCellFillStrategy == ccRasterGrid::INTERPOLATE, ccRasterGrid::INVALID_PROJECTION_TYPE, pDlg.data())) { groundRaster.fillEmptyCells(emptyCellFillStrategy, groundHeight); ccLog::Print(QString("[Volume] Ground raster grid: size: %1 x %2 / heights: [%3 ; %4]").arg(groundRaster.width).arg(groundRaster.height).arg(groundRaster.minHeight).arg(groundRaster.maxHeight)); } else { return false; } } //ceil ccRasterGrid ceilRaster; if (ceil) { if (!ceilRaster.init(gridWidth, gridHeight, gridStep, minCorner)) { //not enough memory return SendError("Not enough memory", parentWidget); } if (ceilRaster.fillWith(ceil, vertDim, projectionType, emptyCellFillStrategy == ccRasterGrid::INTERPOLATE, ccRasterGrid::INVALID_PROJECTION_TYPE, pDlg.data())) { ceilRaster.fillEmptyCells(emptyCellFillStrategy, ceilHeight); ccLog::Print(QString("[Volume] Ceil raster grid: size: %1 x %2 / heights: [%3 ; %4]").arg(ceilRaster.width).arg(ceilRaster.height).arg(ceilRaster.minHeight).arg(ceilRaster.maxHeight)); } else { return false; } } //update grid and compute volume { if (pDlg) { pDlg->setMethodTitle(QObject::tr("Volume computation")); pDlg->setInfo(QObject::tr("Cells: %1 x %2").arg(grid.width).arg(grid.height)); pDlg->start(); pDlg->show(); QCoreApplication::processEvents(); } CCLib::NormalizedProgress nProgress(pDlg.data(), grid.width * grid.height); size_t ceilNonMatchingCount = 0; size_t groundNonMatchingCount = 0; size_t cellCount = 0; //at least one of the grid is based on a cloud grid.nonEmptyCellCount = 0; for (unsigned i = 0; i < grid.height; ++i) { for (unsigned j = 0; j < grid.width; ++j) { ccRasterCell& cell = grid.rows[i][j]; bool validGround = true; cell.minHeight = groundHeight; if (ground) { cell.minHeight = groundRaster.rows[i][j].h; validGround = std::isfinite(cell.minHeight); } bool validCeil = true; cell.maxHeight = ceilHeight; if (ceil) { cell.maxHeight = ceilRaster.rows[i][j].h; validCeil = std::isfinite(cell.maxHeight); } if (validGround && validCeil) { cell.h = cell.maxHeight - cell.minHeight; cell.nbPoints = 1; reportInfo.volume += cell.h; if (cell.h < 0) { reportInfo.removedVolume -= cell.h; } else if (cell.h > 0) { reportInfo.addedVolume += cell.h; } reportInfo.surface += 1.0; ++grid.nonEmptyCellCount; //= matching count ++cellCount; } else { if (validGround) { ++cellCount; ++groundNonMatchingCount; } else if (validCeil) { ++cellCount; ++ceilNonMatchingCount; } cell.h = std::numeric_limits<double>::quiet_NaN(); cell.nbPoints = 0; } cell.avgHeight = (groundHeight + ceilHeight) / 2; cell.stdDevHeight = 0; if (pDlg && !nProgress.oneStep()) { ccLog::Warning("[Volume] Process cancelled by the user"); return false; } } } grid.validCellCount = grid.nonEmptyCellCount; //count the average number of valid neighbors { size_t validNeighborsCount = 0; size_t count = 0; for (unsigned i = 1; i < grid.height - 1; ++i) { for (unsigned j = 1; j < grid.width - 1; ++j) { ccRasterCell& cell = grid.rows[i][j]; if (cell.h == cell.h) { for (unsigned k = i - 1; k <= i + 1; ++k) { for (unsigned l = j - 1; l <= j + 1; ++l) { if (k != i || l != j) { ccRasterCell& otherCell = grid.rows[k][l]; if (std::isfinite(otherCell.h)) { ++validNeighborsCount; } } } } ++count; } } } if (count) { reportInfo.averageNeighborsPerCell = static_cast<double>(validNeighborsCount) / count; } } reportInfo.matchingPrecent = static_cast<float>(grid.validCellCount * 100) / cellCount; reportInfo.groundNonMatchingPercent = static_cast<float>(groundNonMatchingCount * 100) / cellCount; reportInfo.ceilNonMatchingPercent = static_cast<float>(ceilNonMatchingCount * 100) / cellCount; float cellArea = static_cast<float>(grid.gridStep * grid.gridStep); reportInfo.volume *= cellArea; reportInfo.addedVolume *= cellArea; reportInfo.removedVolume *= cellArea; reportInfo.surface *= cellArea; } grid.setValid(true); return true; }
static bool ResolveNormalsWithMST(ccPointCloud* cloud, const Graph& graph, CCLib::GenericProgressCallback* progressCb = 0) { assert(cloud && cloud->hasNormals()); //#define COLOR_PATCHES #ifdef COLOR_PATCHES //Test: color patches cloud->setRGBColor(ccColor::white); cloud->showColors(true); //Test: arrival time int sfIdx = cloud->getScalarFieldIndexByName("MST arrival time"); if (sfIdx < 0) sfIdx = cloud->addScalarField("MST arrival time"); ccScalarField* sf = static_cast<ccScalarField*>(cloud->getScalarField(sfIdx)); sf->fill(NAN_VALUE); cloud->setCurrentDisplayedScalarField(sfIdx); #endif //reset std::priority_queue<Edge> priorityQueue; std::vector<bool> visited; unsigned visitedCount = 0; size_t vertexCount = graph.vertexCount(); //instantiate the 'visited' table try { visited.resize(vertexCount,false); } catch (const std::bad_alloc&) { //not enough memory return false; } //progress notification CCLib::NormalizedProgress nProgress(progressCb,static_cast<unsigned>(vertexCount)); if (progressCb) { progressCb->reset(); progressCb->setMethodTitle("Orient normals (MST)"); progressCb->setInfo(qPrintable(QString("Compute Minimum spanning tree\nPoints: %1\nEdges: %2").arg(vertexCount).arg(graph.edgeCount()))); progressCb->start(); } //while unvisited vertices remain... size_t firstUnvisitedIndex = 0; size_t patchCount = 0; size_t inversionCount = 0; while (visitedCount < vertexCount) { //find the first not-yet-visited vertex while (visited[firstUnvisitedIndex]) ++firstUnvisitedIndex; //set it as "visited" { visited[firstUnvisitedIndex] = true; ++visitedCount; //add its neighbors to the priority queue const std::set<size_t>& neighbors = graph.getVertexNeighbors(firstUnvisitedIndex); for (std::set<size_t>::const_iterator it = neighbors.begin(); it != neighbors.end(); ++it) priorityQueue.push(Edge(firstUnvisitedIndex, *it, graph.weight(firstUnvisitedIndex, *it))); if (progressCb && !nProgress.oneStep()) break; } #ifdef COLOR_PATCHES ccColor::Rgb patchCol = ccColor::Generator::Random(); cloud->setPointColor(static_cast<unsigned>(firstUnvisitedIndex), patchCol.rgb); sf->setValue(static_cast<unsigned>(firstUnvisitedIndex),static_cast<ScalarType>(visitedCount)); #endif while(!priorityQueue.empty() && visitedCount < vertexCount) { //process next edge (with the lowest 'weight') Edge element = priorityQueue.top(); priorityQueue.pop(); //there should only be (at most) one unvisited vertex in the edge size_t v = 0; if (!visited[element.v1()]) v = element.v1(); else if (!visited[element.v2()]) v = element.v2(); else continue; //invert normal if necessary (DO THIS BEFORE SETTING THE VERTEX AS 'VISITED'!) const CCVector3& N1 = cloud->getPointNormal(static_cast<unsigned>(element.v1())); const CCVector3& N2 = cloud->getPointNormal(static_cast<unsigned>(element.v2())); if (N1.dot(N2) < 0) { if (!visited[element.v1()]) { cloud->setPointNormal(static_cast<unsigned>(v), -N1); } else { cloud->setPointNormal(static_cast<unsigned>(v), -N2); } ++inversionCount; } //set it as "visited" { visited[v] = true; ++visitedCount; //add its neighbors to the priority queue const std::set<size_t>& neighbors = graph.getVertexNeighbors(v); for (std::set<size_t>::const_iterator it = neighbors.begin(); it != neighbors.end(); ++it) priorityQueue.push(Edge(v, *it, graph.weight(v,*it))); } #ifdef COLOR_PATCHES cloud->setPointColor(static_cast<unsigned>(v), patchCol); sf->setValue(static_cast<unsigned>(v),static_cast<ScalarType>(visitedCount)); #endif if (progressCb && !nProgress.oneStep()) { visitedCount = static_cast<unsigned>(vertexCount); //early stop break; } } //new patch ++patchCount; } #ifdef COLOR_PATCHES sf->computeMinAndMax(); cloud->showSF(true); #endif if (progressCb) { progressCb->stop(); } ccLog::Print(QString("[ResolveNormalsWithMST] Patches = %1 / Inversions: %2").arg(patchCount).arg(inversionCount)); return true; }
bool ccVolumeCalcTool::updateGrid() { if (!m_cloud2) { assert(false); return false; } //cloud bounding-box --> grid size ccBBox box = getCustomBBox(); if (!box.isValid()) { return false; } unsigned gridWidth = 0, gridHeight = 0; if (!getGridSize(gridWidth, gridHeight)) { return false; } //grid size unsigned gridTotalSize = gridWidth * gridHeight; if (gridTotalSize == 1) { if (QMessageBox::question(this, "Unexpected grid size", "The generated grid will only have 1 cell! Do you want to proceed anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return false; } else if (gridTotalSize > 10000000) { if (QMessageBox::question(this, "Big grid size", "The generated grid will have more than 10.000.000 cells! Do you want to proceed anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return false; } //grid step double gridStep = getGridStep(); assert(gridStep != 0); //memory allocation CCVector3d minCorner = CCVector3d::fromArray(box.minCorner().u); if (!m_grid.init(gridWidth, gridHeight, gridStep, minCorner)) { //not enough memory ccLog::Error("Not enough memory"); return false; } //ground ccGenericPointCloud* groundCloud = 0; double groundHeight = 0; switch (groundComboBox->currentIndex()) { case 0: groundHeight = groundEmptyValueDoubleSpinBox->value(); break; case 1: groundCloud = m_cloud1 ? m_cloud1 : m_cloud2; break; case 2: groundCloud = m_cloud2; break; default: assert(false); return false; } //vertical dimension const unsigned char Z = getProjectionDimension(); assert(Z >= 0 && Z <= 2); //per-cell Z computation ccRasterGrid::ProjectionType projectionType = getTypeOfProjection(); ccProgressDialog pDlg(true, this); ccRasterGrid groundRaster; if (groundCloud) { if (!groundRaster.init(gridWidth, gridHeight, gridStep, minCorner)) { //not enough memory ccLog::Error("Not enough memory"); return false; } if (groundRaster.fillWith( groundCloud, Z, projectionType, getFillEmptyCellsStrategy(fillGroundEmptyCellsComboBox) == ccRasterGrid::INTERPOLATE, ccRasterGrid::INVALID_PROJECTION_TYPE, &pDlg)) { groundRaster.fillEmptyCells(getFillEmptyCellsStrategy(fillGroundEmptyCellsComboBox), groundEmptyValueDoubleSpinBox->value()); ccLog::Print(QString("[Volume] Ground raster grid: size: %1 x %2 / heights: [%3 ; %4]").arg(m_grid.width).arg(m_grid.height).arg(m_grid.minHeight).arg(m_grid.maxHeight)); } else { return false; } } //ceil ccGenericPointCloud* ceilCloud = 0; double ceilHeight = 0; switch (ceilComboBox->currentIndex()) { case 0: ceilHeight = ceilEmptyValueDoubleSpinBox->value(); break; case 1: ceilCloud = m_cloud1 ? m_cloud1 : m_cloud2; break; case 2: ceilCloud = m_cloud2; break; default: assert(false); return false; } ccRasterGrid ceilRaster; if (ceilCloud) { if (!ceilRaster.init(gridWidth, gridHeight, gridStep, minCorner)) { //not enough memory ccLog::Error("Not enough memory"); return false; } if (ceilRaster.fillWith(ceilCloud, Z, projectionType, getFillEmptyCellsStrategy(fillCeilEmptyCellsComboBox) == ccRasterGrid::INTERPOLATE, ccRasterGrid::INVALID_PROJECTION_TYPE, &pDlg)) { ceilRaster.fillEmptyCells(getFillEmptyCellsStrategy(fillCeilEmptyCellsComboBox), ceilEmptyValueDoubleSpinBox->value()); ccLog::Print(QString("[Volume] Ceil raster grid: size: %1 x %2 / heights: [%3 ; %4]").arg(m_grid.width).arg(m_grid.height).arg(m_grid.minHeight).arg(m_grid.maxHeight)); } else { return false; } } //update grid and compute volume { pDlg.setMethodTitle(tr("Volume computation")); pDlg.setInfo(tr("Cells: %1 x %2").arg(m_grid.width).arg(m_grid.height)); pDlg.start(); pDlg.show(); QCoreApplication::processEvents(); CCLib::NormalizedProgress nProgress(&pDlg, m_grid.width*m_grid.height); ReportInfo repotInfo; size_t ceilNonMatchingCount = 0; size_t groundNonMatchingCount = 0; size_t cellCount = 0; //at least one of the grid is based on a cloud m_grid.nonEmptyCellCount = 0; for (unsigned i = 0; i < m_grid.height; ++i) { for (unsigned j = 0; j < m_grid.width; ++j) { ccRasterCell& cell = m_grid.rows[i][j]; bool validGround = true; cell.minHeight = groundHeight; if (groundCloud) { cell.minHeight = groundRaster.rows[i][j].h; validGround = std::isfinite(cell.minHeight); } bool validCeil = true; cell.maxHeight = ceilHeight; if (ceilCloud) { cell.maxHeight = ceilRaster.rows[i][j].h; validCeil = std::isfinite(cell.maxHeight); } if (validGround && validCeil) { cell.h = cell.maxHeight - cell.minHeight; cell.nbPoints = 1; repotInfo.volume += cell.h; if (cell.h < 0) { repotInfo.removedVolume -= cell.h; } else if (cell.h > 0) { repotInfo.addedVolume += cell.h; } repotInfo.surface += 1.0; ++m_grid.nonEmptyCellCount; //= matching count ++cellCount; } else { if (validGround) { ++cellCount; ++groundNonMatchingCount; } else if (validCeil) { ++cellCount; ++ceilNonMatchingCount; } cell.h = std::numeric_limits<double>::quiet_NaN(); cell.nbPoints = 0; } cell.avgHeight = (groundHeight + ceilHeight) / 2; cell.stdDevHeight = 0; if (!nProgress.oneStep()) { ccLog::Warning("[Volume] Process cancelled by the user"); return false; } } } m_grid.validCellCount = m_grid.nonEmptyCellCount; //count the average number of valid neighbors { size_t validNeighborsCount = 0; size_t count = 0; for (unsigned i = 1; i < m_grid.height - 1; ++i) { for (unsigned j = 1; j < m_grid.width - 1; ++j) { ccRasterCell& cell = m_grid.rows[i][j]; if (cell.h == cell.h) { for (unsigned k = i - 1; k <= i + 1; ++k) { for (unsigned l = j - 1; l <= j + 1; ++l) { if (k != i || l != j) { ccRasterCell& otherCell = m_grid.rows[k][l]; if (std::isfinite(otherCell.h)) { ++validNeighborsCount; } } } } ++count; } } } if (count) { repotInfo.averageNeighborsPerCell = static_cast<double>(validNeighborsCount) / count; } } repotInfo.matchingPrecent = static_cast<float>(m_grid.validCellCount * 100) / cellCount; repotInfo.groundNonMatchingPercent = static_cast<float>(groundNonMatchingCount * 100) / cellCount; repotInfo.ceilNonMatchingPercent = static_cast<float>(ceilNonMatchingCount * 100) / cellCount; float cellArea = static_cast<float>(m_grid.gridStep * m_grid.gridStep); repotInfo.volume *= cellArea; repotInfo.addedVolume *= cellArea; repotInfo.removedVolume *= cellArea; repotInfo.surface *= cellArea; outputReport(repotInfo); } m_grid.setValid(true); return true; }
int FastMarchingForFacetExtraction::init( ccGenericPointCloud* cloud, CCLib::DgmOctree* theOctree, unsigned char level, ScalarType maxError, CCLib::DistanceComputationTools::ERROR_MEASURES errorMeasure, bool useRetroProjectionError, CCLib::GenericProgressCallback* progressCb/*=0*/) { m_maxError = maxError; m_errorMeasure = errorMeasure; m_useRetroProjectionError = useRetroProjectionError; if (progressCb) { if (progressCb->textCanBeEdited()) { progressCb->setMethodTitle("Fast Marching grid initialization"); progressCb->setInfo(qPrintable(QString("Level: %1").arg(level))); } progressCb->update(0); progressCb->start(); } int result = initGridWithOctree(theOctree, level); if (result < 0) return result; //fill the grid with the octree CCLib::DgmOctree::cellCodesContainer cellCodes; theOctree->getCellCodes(level,cellCodes,true); size_t cellCount = cellCodes.size(); CCLib::NormalizedProgress nProgress(progressCb, static_cast<unsigned>(cellCount)); if (progressCb) { progressCb->setInfo(qPrintable(QString("Level: %1\nCells: %2").arg(level).arg(cellCount))); } CCLib::ReferenceCloud Yk(theOctree->associatedCloud()); while (!cellCodes.empty()) { if (theOctree->getPointsInCell(cellCodes.back(),level,&Yk,true)) { //convert the octree cell code to grid position Tuple3i cellPos; theOctree->getCellPos(cellCodes.back(),level,cellPos,true); CCVector3 N,C; ScalarType error; if (ComputeCellStats(&Yk,N,C,error,m_errorMeasure)) { //convert octree cell pos to FM cell pos index unsigned gridPos = pos2index(cellPos); //create corresponding cell PlanarCell* aCell = new PlanarCell; aCell->cellCode = cellCodes.back(); aCell->N = N; aCell->C = C; aCell->planarError = error; m_theGrid[gridPos] = aCell; } else { //an error occurred?! return -10; } } cellCodes.pop_back(); if (progressCb && !nProgress.oneStep()) { //process cancelled by user progressCb->stop(); return -1; } } if (progressCb) { progressCb->stop(); } m_initialized = true; return 0; }
void ccRasterizeTool::generateContours() { if (!m_grid.isValid() || !m_rasterCloud) { ccLog::Error("Need a valid raster/cloud to compute contours!"); return; } ccScalarField* activeLayer = m_rasterCloud->getCurrentDisplayedScalarField(); if (!activeLayer) { ccLog::Error("No valid/active layer!"); return; } double startValue = contourStartDoubleSpinBox->value(); if (startValue > activeLayer->getMax()) { ccLog::Error("Start value is above the layer maximum value!"); return; } double step = contourStepDoubleSpinBox->value(); assert(step > 0); unsigned levelCount = 1 + static_cast<unsigned>(floor((activeLayer->getMax()-startValue)/step)); removeContourLines(); bool ignoreBorders = ignoreContourBordersCheckBox->isChecked(); unsigned xDim = m_grid.width; unsigned yDim = m_grid.height; int margin = 0; if (!ignoreBorders) { margin = 1; xDim += 2; yDim += 2; } std::vector<double> grid; try { grid.resize(xDim * yDim, 0); } catch (const std::bad_alloc&) { ccLog::Error("Not enough memory!"); if (m_window) m_window->redraw(); return; } //fill grid { bool sparseLayer = (activeLayer->currentSize() != m_grid.height * m_grid.width); double emptyCellsValue = activeLayer->getMin()-1.0; unsigned layerIndex = 0; for (unsigned j=0; j<m_grid.height; ++j) { RasterCell* cell = m_grid.data[j]; double* row = &(grid[(j+margin)*xDim + margin]); for (unsigned i=0; i<m_grid.width; ++i) { if (cell[i].nbPoints || !sparseLayer) { ScalarType value = activeLayer->getValue(layerIndex++); row[i] = ccScalarField::ValidValue(value) ? value : emptyCellsValue; } else { row[i] = emptyCellsValue; } } } } bool memoryError = false; try { Isolines<double> iso(static_cast<int>(xDim),static_cast<int>(yDim)); if (!ignoreBorders) iso.createOnePixelBorder(&(grid.front()),activeLayer->getMin()-1.0); //bounding box ccBBox box = getCustomBBox(); assert(box.isValid()); //vertical dimension const unsigned char Z = getProjectionDimension(); assert(Z >= 0 && Z <= 2); const unsigned char X = Z == 2 ? 0 : Z +1; const unsigned char Y = X == 2 ? 0 : X +1; int minVertexCount = minVertexCountSpinBox->value(); assert(minVertexCount >= 3); ccProgressDialog pDlg(true,this); pDlg.setMethodTitle("Contour plot"); pDlg.setInfo(qPrintable(QString("Levels: %1\nCells: %2 x %3").arg(levelCount).arg(m_grid.width).arg(m_grid.height))); pDlg.start(); pDlg.show(); QApplication::processEvents(); CCLib::NormalizedProgress nProgress(&pDlg,levelCount); int lineWidth = contourWidthSpinBox->value(); bool colorize = colorizeContoursCheckBox->isChecked(); double v = startValue; while (v <= activeLayer->getMax() && !memoryError) { //extract contour lines for the current level iso.setThreshold(v); int lineCount = iso.find(&(grid.front())); ccLog::PrintDebug(QString("[Rasterize][Isolines] value=%1 : %2 lines").arg(v).arg(lineCount)); //convert them to poylines int realCount = 0; for (int i=0; i<lineCount; ++i) { int vertCount = iso.getContourLength(i); if (vertCount >= minVertexCount) { ccPointCloud* vertices = new ccPointCloud("vertices"); ccPolyline* poly = new ccPolyline(vertices); poly->addChild(vertices); bool isClosed = iso.isContourClosed(i); if (poly->reserve(vertCount) && vertices->reserve(vertCount)) { unsigned localIndex = 0; for (int vi=0; vi<vertCount; ++vi) { double x = iso.getContourX(i,vi) - margin + 0.5; double y = iso.getContourY(i,vi) - margin + 0.5; CCVector3 P; P.u[X] = static_cast<PointCoordinateType>(x * m_grid.gridStep + box.minCorner().u[X]); P.u[Y] = static_cast<PointCoordinateType>(y * m_grid.gridStep + box.minCorner().u[Y]); P.u[Z] = static_cast<PointCoordinateType>(v); vertices->addPoint(P); assert(localIndex < vertices->size()); poly->addPointIndex(localIndex++); } assert(poly); if (poly->size() > 1) { poly->setName(QString("Contour line value=%1 (#%2)").arg(v).arg(++realCount)); poly->setGlobalScale(m_cloud->getGlobalScale()); poly->setGlobalShift(m_cloud->getGlobalShift()); poly->setWidth(lineWidth); poly->setClosed(isClosed); //if we have less vertices, it means we have 'chopped' the original contour poly->setColor(ccColor::darkGrey); if (colorize) { const ColorCompType* col = activeLayer->getColor(v); if (col) poly->setColor(ccColor::Rgb(col)); } poly->showColors(true); vertices->setEnabled(false); //add the 'const altitude' meta-data as well poly->setMetaData(ccPolyline::MetaKeyConstAltitude(),QVariant(v)); if (m_window) m_window->addToOwnDB(poly); m_contourLines.push_back(poly); } else { delete poly; poly = 0; } } else { delete poly; poly = 0; ccLog::Error("Not enough memory!"); memoryError = true; //early stop break; } } } v += step; if (!nProgress.oneStep()) { //process cancelled by user break; } } } catch (const std::bad_alloc&) { ccLog::Error("Not enough memory!"); } ccLog::Print(QString("[Rasterize] %1 iso-lines generated (%2 levels)").arg(m_contourLines.size()).arg(levelCount)); if (!m_contourLines.empty()) { if (memoryError) { removeContourLines(); } else { exportContoursPushButton->setEnabled(true); clearContoursPushButton->setEnabled(true); } } if (m_window) m_window->redraw(); }