#include "StdAfx.h" #include "KEDAlgorithm.h" #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif template void qDeleteAll(std::vector& vec) { for (auto* p : vec) { delete p; } vec.clear(); } // 将 std::string 转为 std::wstring static std::wstring utf8_to_wstring(const std::string& s) { if (s.empty()) return {}; int needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), NULL, 0); std::wstring out(needed, 0); MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &out[0], needed); return out; } // 将本地多字节编码 (GBK/ANSI) 转成 Unicode static std::wstring ansi_to_wstring(const std::string& s) { if (s.empty()) return {}; int needed = MultiByteToWideChar(CP_ACP, 0, s.c_str(), (int)s.size(), NULL, 0); std::wstring out(needed, 0); MultiByteToWideChar(CP_ACP, 0, s.c_str(), (int)s.size(), &out[0], needed); return out; } // 运行 facies_boundaries.exe bool RunFaciesBoundaries( const std::string& exePath, const std::string& grdPath, const std::string& dfdPath, std::string& outStdout, DWORD& outExitCode, DWORD timeoutMs, std::atomic* cancelFlag = nullptr) { outStdout.clear(); outExitCode = (DWORD)-1; // 构造命令行(不变) std::wstringstream cmd; cmd << L"\"" << ansi_to_wstring(exePath) << L"\" "; cmd << L"\"" << ansi_to_wstring(grdPath) << L"\" "; cmd << L"\"" << ansi_to_wstring(dfdPath) << L"\""; SECURITY_ATTRIBUTES sa{ sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; HANDLE hRead = NULL, hWrite = NULL; if (!CreatePipe(&hRead, &hWrite, &sa, 0)) return false; SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0); STARTUPINFOW si = { 0 }; PROCESS_INFORMATION pi = { 0 }; si.cb = sizeof(si); si.hStdOutput = hWrite; si.hStdError = hWrite; si.dwFlags |= STARTF_USESTDHANDLES; std::wstring cmdStr = cmd.str(); LPWSTR cmdLine = &cmdStr[0]; BOOL ok = CreateProcessW( NULL, cmdLine, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi ); CloseHandle(hWrite); // 父进程不再写 if (!ok) { CloseHandle(hRead); return false; } //关键:循环等待,期间检查 cancelFlag const DWORD pollInterval = 100; // 每 100ms 检查一次 DWORD totalWait = 0; bool wasCancelled = false; while (true) { // 检查是否被外部请求取消 if (cancelFlag && cancelFlag->load()) { TerminateProcess(pi.hProcess, 1); wasCancelled = true; break; } // 检查是否超时 if (timeoutMs != INFINITE && totalWait >= timeoutMs) { TerminateProcess(pi.hProcess, 1); wasCancelled = true; break; } // 检查进程是否已退出 DWORD exitCode; if (GetExitCodeProcess(pi.hProcess, &exitCode) && exitCode != STILL_ACTIVE) { outExitCode = exitCode; break; } // 等待一小段时间 Sleep(pollInterval); totalWait += pollInterval; } // 读取输出(即使被取消,也尽量读完缓冲区) const DWORD bufSize = 4096; char buffer[bufSize]; DWORD bytesRead = 0; std::string output; while (ReadFile(hRead, buffer, bufSize, &bytesRead, NULL) && bytesRead > 0) { output.append(buffer, bytesRead); } outStdout = output; // 如果是被取消的,返回 false bool success = !wasCancelled && (outExitCode == 0); // 清理句柄 CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hRead); return success; } // 计算多边形包围盒 static inline void ComputePolygonBBox(const CCurveEx* poly, double& xmin, double& xmax, double& ymin, double& ymax) { xmin = 1e20; xmax = -1e20; ymin = 1e20; ymax = -1e20; for (int i = 0; i < poly->num; ++i) { xmin = std::min(xmin, (double)poly->x[i]); xmax = std::max(xmax, (double)poly->x[i]); ymin = std::min(ymin, (double)poly->y[i]); ymax = std::max(ymax, (double)poly->y[i]); } } KEDAlgorithm::KEDAlgorithm() { m_DepositionalMap.clear(); } KEDAlgorithm::~KEDAlgorithm() { } bool KEDAlgorithm::ProcessExistingGrd( const CString& grdFile, // 已存在的 GRD 文件 const CString& kevFile, // 输出 KEV 文件 CColorBase base, bool isFaciesBorder, std::function progressCallback) { const int TOTAL_STEPS = 4; auto reportProgress = [&](int step, bool failed = false) { if (progressCallback) progressCallback(failed ? -1 : step, TOTAL_STEPS); }; m_cancelRequested.store(false); // 检查 GRD 文件 std::ifstream fchk(grdFile); if (!fchk.is_open()) { reportProgress(0, true); return false; } fchk.close(); if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(1); std::shared_ptr pXy = nullptr; std::vector pCurves; if (isFaciesBorder) { // --- 准备路径 --- CSplitPath sp; sp.SetModuleFileName(); CString xmlPath; xmlPath.Format("%s%s", sp.GetPath(), "facies"); sp.MakeDirectory(xmlPath); CString outdfdPath; outdfdPath.Format("%s%s", xmlPath, "\\facies.dfd"); CString exePath; exePath.Format("%s%s", sp.GetPath(), "facies_boundaries.exe"); // --- 调用 EXE --- std::string grdPath = grdFile; std::string dfdPath = std::string(outdfdPath); std::string procOut; DWORD exitCode = 0; bool ran = RunFaciesBoundaries(std::string(exePath), grdPath, dfdPath, procOut, exitCode, 120000, &m_cancelRequested); if (!ran || exitCode != 0) { std::cerr << "无法启动 facies_boundaries.exe\n"; reportProgress(2, true); return false; } reportProgress(2); CFile fw; if (!fw.Open(outdfdPath, CFile::modeRead)) { reportProgress(2, true); return false; } // 初始化外部定义的 pXy pXy = std::make_shared(); pXy->DFD_Read2(fw); fw.Close(); CPositionList list; if (pXy->GetElement("Layer:\\Facies", list) > 0) { for (POSITION pos = list.GetHeadPosition(); pos != NULL; list.GetNext(pos)) { COne* pOne = (COne*)(pXy->GetValueList()->GetAt(list.GetAt(pos))); CCurve* curve = (CCurve*)pOne->GetValue(); pCurves.push_back(curve); } } if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(3); } else { // 如果不需要边界,直接跳过中间步骤,但更新进度 reportProgress(2); reportProgress(3); } // 4. 写入 KEV 文件 if (!GrdToKEVFile(grdFile, kevFile, pCurves, base)) { reportProgress(3, true); return false; } reportProgress(4); return true; } std::vector KEDAlgorithm::LoadFaciesSettings(const CString& settingsFile, std::map& labelMap) { std::vector samples; std::ifstream file((LPCTSTR)settingsFile); if(!file.is_open()) throw std::runtime_error("无法打开文件: " + settingsFile); std::string line; std::getline(file, line); // 跳过表头 while (std::getline(file, line)) { if (line.empty()) continue; std::stringstream ss(line); std::string name, sCode, sFillCol, sBorderCol, sValue, sX, sY; // 严格匹配表头顺序:名称, 代码, 填充色, 边界色, 相值, 横坐标, 纵坐标 if (!std::getline(ss, name, ',')) continue; if (!std::getline(ss, sCode, ',')) continue; if (!std::getline(ss, sFillCol, ',')) continue; if (!std::getline(ss, sBorderCol, ',')) continue; if (!std::getline(ss, sValue, ',')) continue; if (!std::getline(ss, sX, ',')) continue; if (!std::getline(ss, sY, ',')) continue; FaciesConfig config; config.name = name; config.code = sCode; config.fillColor = (COLORREF)std::stoll(sFillCol); // 读取 int 颜色 config.borderColor = (COLORREF)std::stoll(sBorderCol); // 读取 int 颜色 config.value = std::stod(sValue); config.x = std::stod(sX); // 读取物理横坐标 config.y = std::stod(sY); // 读取物理纵坐标 samples.push_back(config); // 设置图例:相代码作为 Z 值,相名称作为显示文本 std::string faciesName; faciesName = name + "-" + sCode; m_DepositionalMap.insert({ faciesName, config.value }); m_ColorList.insert({ config.value , config.fillColor }); } return samples; } std::vector KEDAlgorithm::ReadSamplesCSV(const CString& csvFile, std::map& labelMap) { std::vector samples; std::ifstream file(csvFile); if (!file.is_open()) throw std::runtime_error("无法打开文件: " + csvFile); std::string line; std::getline(file, line); // 跳过表头 while (std::getline(file, line)) { std::stringstream ss(line); std::string sx, sy, sz; if (!std::getline(ss, sx, ',')) continue; if (!std::getline(ss, sy, ',')) continue; if (!std::getline(ss, sz, ',')) continue; if (sx.empty() || sy.empty()) continue; double x = std::stod(sx); double y = std::stod(sy); if (labelMap.find(sz) == labelMap.end()) { int newId = static_cast(labelMap.size()); labelMap[sz] = newId; } m_DepositionalMap.insert({ sz, labelMap[sz] }); samples.push_back({ x, y, labelMap[sz] }); } return samples; } double KEDAlgorithm::KdeLogDensityBrute(const std::vector& samples, int label, double qx, double qy, double bandwidth) const { if (samples.empty()) return -std::numeric_limits::infinity(); double log_coeff = -std::log(2.0 * M_PI) - 2.0 * std::log(bandwidth); double sum_exp = 0.0; int n = 0; for (const auto& s : samples) { if (s.value != label) continue; double dx = qx - s.x; double dy = qy - s.y; double dist2 = dx * dx + dy * dy; double log_p = log_coeff - 0.5 * dist2 / (bandwidth * bandwidth); sum_exp += std::exp(log_p); ++n; } if (n == 0) return -std::numeric_limits::infinity(); return std::log(sum_exp / n); } bool KEDAlgorithm::WriteDSAAFromGrid(const std::string& output_grd, const std::vector& grid_data, int nx, int ny, double minx, double maxx, double miny, double maxy) { if (static_cast(nx * ny) != grid_data.size() || grid_data.empty()) return false; float zmin = *std::min_element(grid_data.begin(), grid_data.end()); float zmax = *std::max_element(grid_data.begin(), grid_data.end()); std::ofstream out(output_grd); if (!out.is_open()) return false; out << "DSAA\n"; out << nx << " " << ny << "\n"; out << minx << " " << maxx << "\n"; out << miny << " " << maxy << "\n"; out << zmin << " " << zmax << "\n"; for (int j = 0; j < ny; ++j) { int src_row = ny - 1 - j; for (int i = 0; i < nx; ++i) { size_t index = static_cast(src_row) * nx + i; out << grid_data[index] << " "; if ((i + 1) % 10 == 0) out << "\n"; } out << "\n"; } return true; } bool KEDAlgorithm::GrdToKEVFile(const CString &gridFile, const CString &outputFile, std::vector pCurves, CColorBase &base) { std::shared_ptr pXy = std::make_shared(); pXy->FromGridAuto(gridFile); CLayer* pLayer = pXy->FindAddLayer("背景"); CPositionList pointList; int index = pXy->GetElement(DOUBLEFOX_MESH, pointList); if (pointList.GetSize() == 0) { return false; } COne* pNewOne = pXy->GetAt(pointList.GetHead()); pNewOne->SetLayer(pLayer); if (!CreateFaciesLines(pXy, pCurves)) { return false; } // 设置颜色 CArray ColorList; base.GetColor(ColorList); if (ColorList.GetSize() > 0) { POSITION pt = pXy->FindFirstElement(DOUBLEFOX_MESH); if (pt == nullptr) { return false; } COne* pOne1 = pXy->GetAt(pt); CMesh* pMesh = (CMesh*)pOne1->GetValue(); pMesh->color.SetColor(ColorList); pMesh->UpdateColorRuler(); } pXy->SaveAsWithExtension(outputFile); return true; } bool KEDAlgorithm::CreateFaciesLines(std::shared_ptr pXy, std::vector pCurves) { CString strLayerMark = _T("Layer:\\沉积相界\\相界"); CLayer* pLayer = pXy->FindAddLayer(strLayerMark); AddFaciesCurves(pXy, pCurves, strLayerMark); return true; } void KEDAlgorithm::AddFaciesCurves(std::shared_ptr pXy, std::vector curves, CString layerName) { for (size_t i = 0; i < curves.size(); i++) { CCurve* pCurve = curves.at(i); if (pCurve == NULL) continue; CCurveEx* ce = new CCurveEx(pCurve->num); for (int i = 0; i < ce->num; i++) { ce->x[i] = pCurve->x[i]; ce->y[i] = pCurve->y[i]; ce->z[i] = pCurve->z[i]; } ce->nPoint = pCurve->nPoint; ce->GetLocation(); POSITION pos = NULL; if (pCurve->name) ce->SetName(pCurve->name); pos = pXy->AddElement(ce, DOUBLEFOX_CURVE); pXy->SetElementLayer(pos, layerName); } } bool KEDAlgorithm::KEDMeshAlgorithm(const CString& csvFile, const CString& kevFile, double grid_interval, double bandwidth, double beta, int anchor_radius, std::function progressCallback) { const int TOTAL_STEPS = 6; auto reportProgress = [&](int step, bool failed = false) { if (progressCallback) { progressCallback(failed ? -1 : step, TOTAL_STEPS); } }; m_cancelRequested.store(false); if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } std::map labelMap; auto samples = LoadFaciesSettings(csvFile, labelMap); if (samples.empty()) { reportProgress(0, true); return false; } reportProgress(1); int num_classes = static_cast(labelMap.size()); if (num_classes == 0) { reportProgress(1, true); return false; } // 范围 double minx = 1e20, maxx = -1e20, miny = 1e20, maxy = -1e20; for (const auto& s : samples) { minx = std::min(minx, s.x); maxx = std::max(maxx, s.x); miny = std::min(miny, s.y); maxy = std::max(maxy, s.y); } int nx = static_cast((maxx - minx) / grid_interval) + 1; int ny = static_cast((maxy - miny) / grid_interval) + 1; std::cout << "Grid nx=" << nx << " ny=" << ny << std::endl; // 概率图 std::vector probMaps; probMaps.reserve(num_classes); for (int c = 0; c < num_classes; ++c) probMaps.emplace_back(cv::Mat::zeros(ny, nx, CV_32F)); // KDE + 概率计算 #pragma omp parallel { std::vector log_densities(num_classes); #pragma omp for collapse(2) schedule(dynamic) for (int iy = 0; iy < ny; ++iy) { if (m_cancelRequested.load()) continue; for (int ix = 0; ix < nx; ++ix) { double gx = minx + ix * grid_interval; double gy = maxy - iy * grid_interval; for (int c = 0; c < num_classes; ++c) log_densities[c] = KdeLogDensityBrute(samples, c, gx, gy, bandwidth); double max_log = *std::max_element(log_densities.begin(), log_densities.end()); if (max_log <= -1e20) { for (int c = 0; c < num_classes; ++c) probMaps[c].at(iy, ix) = 1.0f / num_classes; } else { double sum_exp = 0.0; for (int c = 0; c < num_classes; ++c) if (log_densities[c] > -1e20) sum_exp += std::exp(log_densities[c] - max_log); for (int c = 0; c < num_classes; ++c) { double p = 0.0; if (log_densities[c] > -1e20) p = std::exp(log_densities[c] - max_log) / sum_exp; p = (1.0 - beta) * p + beta * (1.0 / num_classes); probMaps[c].at(iy, ix) = static_cast(p); } } } } } if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(2); // 初始分类 cv::Mat initialGrid(ny, nx, CV_8UC1, cv::Scalar(0)); for (int iy = 0; iy < ny; ++iy) { for (int ix = 0; ix < nx; ++ix) { int best_label = 0; float best_prob = probMaps[0].at(iy, ix); for (int c = 1; c < num_classes; ++c) { float p = probMaps[c].at(iy, ix); if (p > best_prob) { best_prob = p; best_label = c; } } initialGrid.at(iy, ix) = static_cast(best_label); } } if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(3); // 形态学处理 cv::Mat tempGrid = initialGrid.clone(); cv::morphologyEx(tempGrid, tempGrid, cv::MORPH_OPEN, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5))); cv::morphologyEx(tempGrid, tempGrid, cv::MORPH_CLOSE, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(9, 9))); cv::Mat finalGrid = tempGrid.clone(); if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(4); // 锚点加固 for (const auto& s : samples) { int col = static_cast(std::round((s.x - minx) / grid_interval)); int row = static_cast(std::round((maxy - s.y) / grid_interval)); if (col >= 0 && col < finalGrid.cols && row >= 0 && row < finalGrid.rows) { cv::circle(finalGrid, cv::Point(col, row), anchor_radius, cv::Scalar(s.value), -1); } } CSplitPath sp; sp.SetModuleFileName(); CString xmlPath = ""; xmlPath.Format("%s%s", sp.GetPath(), "facies"); sp.MakeDirectory(xmlPath); CString gridFile = ""; gridFile.Format("%s%s", xmlPath, "\\facies.grd"); // 保存 GRD std::vector grid_data; grid_data.reserve(finalGrid.total()); for (int i = 0; i < finalGrid.rows; ++i) for (int j = 0; j < finalGrid.cols; ++j) grid_data.push_back(static_cast(finalGrid.at(i, j))); if (!WriteDSAAFromGrid(std::string(gridFile), grid_data, finalGrid.cols, finalGrid.rows, minx, maxx, miny, maxy)) { reportProgress(5, true); return false; } if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(5); CArray ColorList; CColorBase base; base.Clear(); for (const auto& pair : m_ColorList) { CColorItem colorMesh; colorMesh.SetColor(pair.second); colorMesh.z = pair.first; colorMesh.m_bContinue = true; ColorList.Add(colorMesh); } base.SetColor(ColorList); ProcessExistingGrd(gridFile, kevFile, base, true, progressCallback); reportProgress(6); return true; } bool KEDAlgorithm::IDWMeshAlgorithm(const CString& csvFile, const CString& kevFile, const CString& boundaryFile, double grid_interval, int subblock_count, int idw_neighbors, double idw_power, bool use_shepard, bool smooth_boundary, bool isFaciesBorder, std::function progressCallback) { const int TOTAL_STEPS = 6; auto reportProgress = [&](int step, bool failed = false) { if (progressCallback) progressCallback(failed ? -1 : step, TOTAL_STEPS); }; m_cancelRequested.store(false); //读取样本点 std::map labelMap; auto samples = LoadFaciesSettings(csvFile, labelMap); if (samples.empty()) { reportProgress(0, true); return false; } int num_classes = static_cast(labelMap.size()); reportProgress(1); //计算范围(基于样本点 double minx = 1e20, maxx = -1e20, miny = 1e20, maxy = -1e20; float sample_zmin = std::numeric_limits::max(); float sample_zmax = std::numeric_limits::lowest(); for (const auto& s : samples) { minx = std::min(minx, s.x); maxx = std::max(maxx, s.x); miny = std::min(miny, s.y); maxy = std::max(maxy, s.y); sample_zmin = std::min(sample_zmin, static_cast(s.value)); sample_zmax = std::max(sample_zmax, static_cast(s.value)); } reportProgress(2); //读取边界文件 (仅用于过滤) std::vector vBorder; if (!boundaryFile.IsEmpty()) { vBorder = GetDFDBorder(boundaryFile); } bool use_custom_border = !vBorder.empty(); //扩展范围 minx -= grid_interval; miny -= grid_interval; maxx += grid_interval; maxy += grid_interval; // 构建网格 + KDTree int nx = static_cast((maxx - minx) / grid_interval) + 1; int ny = static_cast((maxy - miny) / grid_interval) + 1; const float NO_DATA_VALUE = 1e301; std::vector grid_data(nx * ny, NO_DATA_VALUE); PointCloud cloud; cloud.pts = samples; typedef KDTreeSingleIndexAdaptor< L2_Simple_Adaptor, PointCloud, 2> my_kd_tree_t; my_kd_tree_t index(2, cloud, KDTreeSingleIndexAdaptorParams(10)); index.buildIndex(); reportProgress(3); // ================= 新增:建立物理值与数组索引的映射 ================= std::vector uniqueValues; std::map valueToIndex; int vIdx = 0; for (const auto& pair : m_ColorList) { uniqueValues.push_back(pair.first); // 存储物理相值 valueToIndex[pair.first] = vIdx++; // 物理相值 -> 数组下标 (0, 1, 2...) } // 确保 num_classes 与实际颜色表大小一致 num_classes = static_cast(uniqueValues.size()); // ================================================================== // 分块并行 IDW 插值 int block_h = ny / subblock_count; std::atomic completed_rows{ 0 }; int total_rows = ny; #pragma omp parallel for schedule(dynamic) for (int by = 0; by < subblock_count; ++by) { int startY = by * block_h; int endY = (by == subblock_count - 1) ? ny : (by + 1) * block_h; std::vector ret_indexes(idw_neighbors); std::vector out_dists_sqr(idw_neighbors); std::vector class_weights_local(num_classes); for (int iy = startY; iy < endY; ++iy) { if (m_cancelRequested.load()) break; for (int ix = 0; ix < nx; ++ix) { double qx = minx + ix * grid_interval; double qy = maxy - iy * grid_interval; // 边界限制 if (use_custom_border && !IsPointInPolygon(qx, qy, vBorder)) continue; double query_pt[2] = { qx, qy }; nanoflann::KNNResultSet resultSet(idw_neighbors); resultSet.init(&ret_indexes[0], &out_dists_sqr[0]); index.findNeighbors(resultSet, &query_pt[0]); std::fill(class_weights_local.begin(), class_weights_local.end(), 0.0); double R = sqrt(out_dists_sqr.back()) + 1e-9; for (int k = 0; k < idw_neighbors; ++k) { double d = sqrt(out_dists_sqr[k]) + 1e-9; double w = 0.0; if (use_shepard) { w = pow((R - d) / (R * d + 1e-9), 2); } else { if (idw_power == 2.0) { w = 1.0 / (d * d); } else { w = 1.0 / pow(d, idw_power); } } int phys_value = cloud.pts[ret_indexes[k]].value; int safe_index = valueToIndex[phys_value]; class_weights_local[safe_index] += w; } int best_idx = 0; double best_w = -1.0; for (int lbl = 0; lbl < num_classes; ++lbl) { if (class_weights_local[lbl] > best_w) { best_idx = lbl; best_w = class_weights_local[lbl]; } } grid_data[iy * nx + ix] = static_cast(uniqueValues[best_idx]); } //动态行进度回传 int done = ++completed_rows; if (progressCallback && (done % (ny / 100) == 0)) { // 每约1%更新 int percent = static_cast((done * 100.0) / total_rows); progressCallback(percent, 100); // 实时百分比模式 } } } if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(4); qDeleteAll(vBorder); // 写入 GRD (过滤无效值计算z范围) CSplitPath sp; sp.SetModuleFileName(); CString xmlPath; xmlPath.Format("%s%s", sp.GetPath(), "facies"); sp.MakeDirectory(xmlPath); CString gridFile; gridFile.Format("%s%s", xmlPath, "\\facies_idw.grd"); WriteDSAAFromGridKeepZRange( std::string(CT2A(gridFile)), grid_data, nx, ny, minx, maxx, miny, maxy, sample_zmin, sample_zmax); reportProgress(5); CArray ColorList; CColorBase base; base.Clear(); for (const auto& pair : m_ColorList) { CColorItem colorMesh; colorMesh.SetColor(pair.second); colorMesh.z = pair.first; colorMesh.m_bContinue = true; ColorList.Add(colorMesh); } base.SetColor(ColorList); //生成 KEV ProcessExistingGrd(gridFile, kevFile, base, isFaciesBorder, progressCallback); reportProgress(6); return true; } void KEDAlgorithm::StopTask() { m_cancelRequested.store(true); } // 后处理(平滑 + 过滤 + 去毛刺) void KEDAlgorithm::PostProcessFaciesBoundaries( std::vector& pCurves, double sigma_smooth, int min_seg_cells, double drop_closed_thresh, double dx, double dy) { std::vector filtered; filtered.reserve(pCurves.size()); for (auto* curve : pCurves) { if (!curve || curve->num < 3) continue; //去除太短线段 double totalLen = 0.0; for (int i = 1; i < curve->num; ++i) { double dx_ = curve->x[i] - curve->x[i - 1]; double dy_ = curve->y[i] - curve->y[i - 1]; totalLen += std::sqrt(dx_ * dx_ + dy_ * dy_); } if (totalLen < min_seg_cells * std::min(dx, dy)) { delete curve; continue; } // 高斯平滑线(几何层面) if (sigma_smooth > 0.1) { std::vector newX(curve->num), newY(curve->num); int r = static_cast(sigma_smooth * 2.0); double sigma2 = sigma_smooth * sigma_smooth; for (int i = 0; i < curve->num; ++i) { double sumW = 0.0, sumX = 0.0, sumY = 0.0; for (int j = std::max(0, i - r); j < std::min(curve->num, i + r + 1); ++j) { double w = std::exp(-0.5 * (std::pow(i - j, 2) / sigma2)); sumW += w; sumX += w * curve->x[j]; sumY += w * curve->y[j]; } newX[i] = sumX / sumW; newY[i] = sumY / sumW; } for (int i = 0; i < curve->num; ++i) { curve->x[i] = newX[i]; curve->y[i] = newY[i]; } } filtered.push_back(curve); } pCurves.swap(filtered); } std::vector KEDAlgorithm::GetDFDBorder(const CString& path) { CString borderFile = path; if (!CFindFileEx::IsFileExists(borderFile)) { return {}; } std::shared_ptr pXy = std::make_shared(); borderFile = borderFile.Trim(); if (borderFile.GetLength() <= 0 || borderFile == "NULL") { return {}; } if (!pXy->ReadWithExtension(borderFile)) { return {}; } std::vector result; CPtrList* pl = pXy->GetValueList(); for (POSITION pos = pl->GetHeadPosition(); pos != nullptr; pl->GetNext(pos)) { COne* pOne = (COne*)pl->GetAt(pos); if (pOne->GetType() == DOUBLEFOX_CURVE) { CCurveEx* pCurve = (CCurveEx*)(pOne->GetValue()); CCurveEx* pCurveNew = new CCurveEx(); *pCurveNew = *pCurve; result.push_back(pCurveNew); } } return result; } /** * @brief 批量判断点是否在任意多边形内 * @param qx 检查点的 X 坐标 * @param qy 检查点的 Y 坐标 * @return 如果点在内部则返回 true,否则返回 false */ bool KEDAlgorithm::IsPointInPolygon(double qx, double qy, const std::vector& polygons) { // 如果没有边界,则认为点在边界内 if (polygons.empty()) return true; for (const auto* poly : polygons) { if (!poly || poly->num < 3) continue; int crossings = 0; int num_vertices = poly->num; // 处理闭合多边形(首尾点可能重复) if (poly->x[0] == poly->x[num_vertices - 1] && poly->y[0] == poly->y[num_vertices - 1]) { num_vertices--; } for (int i = 0, j = num_vertices - 1; i < num_vertices; j = i++) { double xi = poly->x[i], yi = poly->y[i]; double xj = poly->x[j], yj = poly->y[j]; // 射线法:从 qx 向正 X 方向发出射线,统计与多边形边的交点数 if (((yi > qy) != (yj > qy)) && // 线段跨越水平线 y=qy (qx < (xj - xi) * (qy - yi) / (yj - yi) + xi)) // 检查交点是否在 qx 右侧 { crossings++; } } // 奇数交点数表示点在内部 if (crossings % 2 == 1) return true; } // 遍历所有多边形都不在内部 return false; } bool KEDAlgorithm::WriteDSAAFromGridKeepZRange(const std::string& output_grd, const std::vector& grid_data, int nx, int ny, double minx, double maxx, double miny, double maxy, float zmin, float zmax) { if (static_cast(nx * ny) != grid_data.size() || grid_data.empty()) return false; std::ofstream out(output_grd); if (!out.is_open()) return false; out << "DSAA\n"; out << nx << " " << ny << "\n"; out << minx << " " << maxx << "\n"; out << miny << " " << maxy << "\n"; out << zmin << " " << zmax << "\n"; for (int j = 0; j < ny; ++j) { int src_row = ny - 1 - j; for (int i = 0; i < nx; ++i) { size_t index = static_cast(src_row) * nx + i; out << grid_data[index] << " "; if ((i + 1) % 10 == 0) out << "\n"; } out << "\n"; } return true; } std::unordered_map KEDAlgorithm::GetDepositionalMap() { return m_DepositionalMap; } bool KEDAlgorithm::NNMeshAlgorithm(const CString& csvFile, const CString& kevFile, const CString& boundaryFile, double grid_interval, double sigma, int open_iter, int close_iter, bool isFaciesBorder, std::function progressCallback) { const int TOTAL_STEPS = 6; auto reportProgress = [&](int step, bool failed = false) { if (progressCallback) progressCallback(failed ? -1 : step, TOTAL_STEPS); }; m_cancelRequested.store(false); std::map labelMap; auto samples = LoadFaciesSettings(csvFile, labelMap); if (samples.empty()) { reportProgress(0, true); return false; } int num_classes = static_cast(labelMap.size()); reportProgress(1); double minx = 1e20, maxx = -1e20, miny = 1e20, maxy = -1e20; float sample_zmin = std::numeric_limits::max(); float sample_zmax = std::numeric_limits::lowest(); for (const auto& s : samples) { minx = std::min(minx, s.x); maxx = std::max(maxx, s.x); miny = std::min(miny, s.y); maxy = std::max(maxy, s.y); sample_zmin = std::min(sample_zmin, (float)s.value); sample_zmax = std::max(sample_zmax, (float)s.value); } // Python: np.arange(start, stop + step*0.1, step) // 保证浮点数计算时涵盖最后一个点 int nx = static_cast(std::floor((maxx - minx + grid_interval * 0.01) / grid_interval)) + 1; int ny = static_cast(std::floor((maxy - miny + grid_interval * 0.01) / grid_interval)) + 1; std::vector vBorder; if (!boundaryFile.IsEmpty()) { vBorder = GetDFDBorder(boundaryFile); } bool use_custom_border = !vBorder.empty(); PointCloud cloud; cloud.pts = samples; typedef KDTreeSingleIndexAdaptor, PointCloud, 2> my_kd_tree_t; my_kd_tree_t index(2, cloud, KDTreeSingleIndexAdaptorParams(10)); index.buildIndex(); reportProgress(2); // 生成初始网格 (Label索引图) cv::Mat gridMat(ny, nx, CV_32SC1); #pragma omp parallel for for (int iy = 0; iy < ny; ++iy) { if (m_cancelRequested.load()) continue; for (int ix = 0; ix < nx; ++ix) { double qx = minx + ix * grid_interval; double qy = maxy - iy * grid_interval; // 注意Y轴方向通常是反的,需确认数据源习惯 double query_pt[2] = { qx, qy }; size_t ret_index; double out_dist_sqr; nanoflann::KNNResultSet resultSet(1); resultSet.init(&ret_index, &out_dist_sqr); index.findNeighbors(resultSet, &query_pt[0]); gridMat.at(iy, ix) = cloud.pts[ret_index].value; } } if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(3); // 1. 提取所有参与计算的物理相值 (std::map 已自动按从小到大排序) std::vector uniqueValues; for (const auto& pair : m_ColorList) { uniqueValues.push_back(pair.first); } int actualNumClasses = static_cast(uniqueValues.size()); // ================= 高斯概率平滑 ================= if (sigma > 0.1 && actualNumClasses > 0) { std::vector probLayers(actualNumClasses); for (int i = 0; i < actualNumClasses; ++i) probLayers[i] = cv::Mat::zeros(ny, nx, CV_32F); // One-Hot Encoding (物理相值映射到平滑层索引) #pragma omp parallel for for (int y = 0; y < ny; ++y) { for (int x = 0; x < nx; ++x) { int currentVal = gridMat.at(y, x); // 找到该物理相值对应的层索引 for (int i = 0; i < actualNumClasses; ++i) { if (uniqueValues[i] == currentVal) { probLayers[i].at(y, x) = 1.0f; break; } } } } // 核大小计算 int radius = static_cast(4.0 * sigma + 0.5); int ksize = 2 * radius + 1; #pragma omp parallel for for (int i = 0; i < actualNumClasses; ++i) { cv::GaussianBlur(probLayers[i], probLayers[i], cv::Size(ksize, ksize), sigma, sigma, cv::BORDER_REFLECT); } // Argmax 重组 (将最高概率的层索引还原为物理相值) #pragma omp parallel for for (int y = 0; y < ny; ++y) { for (int x = 0; x < nx; ++x) { int best_idx = 0; float max_prob = -1.0f; for (int i = 0; i < actualNumClasses; ++i) { float p = probLayers[i].at(y, x); if (p > max_prob) { max_prob = p; best_idx = i; } } // 重组写回真实的相值 gridMat.at(y, x) = uniqueValues[best_idx]; } } } if (m_cancelRequested.load()) { reportProgress(-1, true); return false; } reportProgress(4); // 形态学运算 if (open_iter > 0 || close_iter > 0) { // 使用椭圆核,让相的边界产生地质上的圆润感 cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3)); cv::Mat optimizedGrid = gridMat.clone(); // 核心修正:遍历的是真实的物理相值,而不是 0, 1, 2 for (int i = 0; i < actualNumClasses; ++i) { int physValue = uniqueValues[i]; cv::Mat mask; // 寻找图像中等于 physValue 的像素 cv::inRange(gridMat, cv::Scalar(physValue), cv::Scalar(physValue), mask); if (open_iter > 0) cv::morphologyEx(mask, mask, cv::MORPH_OPEN, element, cv::Point(-1, -1), open_iter); if (close_iter > 0) cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, element, cv::Point(-1, -1), close_iter); // 将平滑后的蒙版区域,使用真实的相值填回去 optimizedGrid.setTo(cv::Scalar(physValue), mask); } gridMat = optimizedGrid; } reportProgress(5); std::vector grid_data; grid_data.reserve(nx * ny); for (int iy = 0; iy < ny; ++iy) { for (int ix = 0; ix < nx; ++ix) { double rx = minx + ix * grid_interval; double ry = maxy - iy * grid_interval; if (use_custom_border && !IsPointInPolygon(rx, ry, vBorder)) { grid_data.push_back(1e301); } else { grid_data.push_back((float)gridMat.at(iy, ix)); } } } qDeleteAll(vBorder); CSplitPath sp; sp.SetModuleFileName(); CString xmlPath; xmlPath.Format("%s%s", sp.GetPath(), "facies"); sp.MakeDirectory(xmlPath); CString gridPath; gridPath.Format("%s%s", xmlPath, "\\facies_nn.grd"); WriteDSAAFromGridKeepZRange( std::string(CT2A(gridPath)), grid_data, nx, ny, minx, maxx, miny, maxy, sample_zmin, sample_zmax); CArray ColorList; CColorBase base; base.Clear(); for (const auto& pair : m_ColorList) { CColorItem colorMesh; colorMesh.SetColor(pair.second); colorMesh.z = pair.first; colorMesh.m_bContinue = true; ColorList.Add(colorMesh); } base.SetColor(ColorList); ProcessExistingGrd(gridPath, kevFile, base, isFaciesBorder, progressCallback); reportProgress(6); return true; }