You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1246 lines
38 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "StdAfx.h"
#include "KEDAlgorithm.h"
#include <windows.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
template <typename T>
void qDeleteAll(std::vector<T*>& 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<bool>* 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<void(int completed, int total)> 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<CXy> pXy = nullptr;
std::vector<CCurve*> 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<CXy>();
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<FaciesConfig> KEDAlgorithm::LoadFaciesSettings(const CString& settingsFile,
std::map<std::string, int>& labelMap)
{
std::vector<FaciesConfig> 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<Sample> KEDAlgorithm::ReadSamplesCSV(const CString& csvFile,
std::map<std::string, int>& labelMap)
{
std::vector<Sample> 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<int>(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<FaciesConfig>& samples,
int label, double qx, double qy,
double bandwidth) const
{
if (samples.empty()) return -std::numeric_limits<double>::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<double>::infinity();
return std::log(sum_exp / n);
}
bool KEDAlgorithm::WriteDSAAFromGrid(const std::string& output_grd,
const std::vector<float>& grid_data,
int nx, int ny,
double minx, double maxx,
double miny, double maxy)
{
if (static_cast<size_t>(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<size_t>(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<CCurve*> pCurves, CColorBase &base)
{
std::shared_ptr<CXy> pXy = std::make_shared<CXy>();
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<CColorItem, CColorItem> 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<CXy> pXy, std::vector<CCurve*> pCurves)
{
CString strLayerMark = _T("Layer:\\沉积相界\\相界");
CLayer* pLayer = pXy->FindAddLayer(strLayerMark);
AddFaciesCurves(pXy, pCurves, strLayerMark);
return true;
}
void KEDAlgorithm::AddFaciesCurves(std::shared_ptr<CXy> pXy, std::vector<CCurve*> 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<void(int completed, int total)> 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<std::string, int> labelMap;
auto samples = LoadFaciesSettings(csvFile, labelMap);
if (samples.empty()) {
reportProgress(0, true);
return false;
}
reportProgress(1);
int num_classes = static_cast<int>(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<int>((maxx - minx) / grid_interval) + 1;
int ny = static_cast<int>((maxy - miny) / grid_interval) + 1;
std::cout << "Grid nx=" << nx << " ny=" << ny << std::endl;
// 概率图
std::vector<cv::Mat> 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<double> 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<float>(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<float>(iy, ix) = static_cast<float>(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<float>(iy, ix);
for (int c = 1; c < num_classes; ++c)
{
float p = probMaps[c].at<float>(iy, ix);
if (p > best_prob) { best_prob = p; best_label = c; }
}
initialGrid.at<uchar>(iy, ix) = static_cast<uchar>(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<int>(std::round((s.x - minx) / grid_interval));
int row = static_cast<int>(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<float> 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<float>(finalGrid.at<uchar>(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<CColorItem, CColorItem> 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<void(int completed, int total)> 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<std::string, int> labelMap;
auto samples = LoadFaciesSettings(csvFile, labelMap);
if (samples.empty()) { reportProgress(0, true); return false; }
int num_classes = static_cast<int>(labelMap.size());
reportProgress(1);
//计算范围(基于样本点
double minx = 1e20, maxx = -1e20, miny = 1e20, maxy = -1e20;
float sample_zmin = std::numeric_limits<float>::max();
float sample_zmax = std::numeric_limits<float>::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<float>(s.value));
sample_zmax = std::max(sample_zmax, static_cast<float>(s.value));
}
reportProgress(2);
//读取边界文件 (仅用于过滤)
std::vector<CCurveEx*> 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<int>((maxx - minx) / grid_interval) + 1;
int ny = static_cast<int>((maxy - miny) / grid_interval) + 1;
const float NO_DATA_VALUE = 1e301;
std::vector<float> grid_data(nx * ny, NO_DATA_VALUE);
PointCloud cloud; cloud.pts = samples;
typedef KDTreeSingleIndexAdaptor<
L2_Simple_Adaptor<double, PointCloud>,
PointCloud, 2> my_kd_tree_t;
my_kd_tree_t index(2, cloud, KDTreeSingleIndexAdaptorParams(10));
index.buildIndex();
reportProgress(3);
// ================= 新增:建立物理值与数组索引的映射 =================
std::vector<int> uniqueValues;
std::map<int, int> 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<int>(uniqueValues.size());
// ==================================================================
// 分块并行 IDW 插值
int block_h = ny / subblock_count;
std::atomic<int> 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<size_t> ret_indexes(idw_neighbors);
std::vector<double> out_dists_sqr(idw_neighbors);
std::vector<double> 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<double> 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<float>(uniqueValues[best_idx]);
}
//动态行进度回传
int done = ++completed_rows;
if (progressCallback && (done % (ny / 100) == 0))
{
// 每约1%更新
int percent = static_cast<int>((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<CColorItem, CColorItem> 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<CCurve*>& pCurves,
double sigma_smooth,
int min_seg_cells,
double drop_closed_thresh,
double dx, double dy)
{
std::vector<CCurve*> 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<double> newX(curve->num), newY(curve->num);
int r = static_cast<int>(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<CCurveEx*> KEDAlgorithm::GetDFDBorder(const CString& path)
{
CString borderFile = path;
if (!CFindFileEx::IsFileExists(borderFile)) {
return {};
}
std::shared_ptr<CXy> pXy = std::make_shared<CXy>();
borderFile = borderFile.Trim();
if (borderFile.GetLength() <= 0 || borderFile == "NULL") {
return {};
}
if (!pXy->ReadWithExtension(borderFile)) {
return {};
}
std::vector<CCurveEx*> 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<CCurveEx*>& 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<float>& grid_data,
int nx, int ny, double minx, double maxx, double miny, double maxy, float zmin, float zmax)
{
if (static_cast<size_t>(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<size_t>(src_row) * nx + i;
out << grid_data[index] << " ";
if ((i + 1) % 10 == 0) out << "\n";
}
out << "\n";
}
return true;
}
std::unordered_map<std::string, int> 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<void(int completed, int total)> 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<std::string, int> labelMap;
auto samples = LoadFaciesSettings(csvFile, labelMap);
if (samples.empty()) { reportProgress(0, true); return false; }
int num_classes = static_cast<int>(labelMap.size());
reportProgress(1);
double minx = 1e20, maxx = -1e20, miny = 1e20, maxy = -1e20;
float sample_zmin = std::numeric_limits<float>::max();
float sample_zmax = std::numeric_limits<float>::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<int>(std::floor((maxx - minx + grid_interval * 0.01) / grid_interval)) + 1;
int ny = static_cast<int>(std::floor((maxy - miny + grid_interval * 0.01) / grid_interval)) + 1;
std::vector<CCurveEx*> vBorder;
if (!boundaryFile.IsEmpty())
{
vBorder = GetDFDBorder(boundaryFile);
}
bool use_custom_border = !vBorder.empty();
PointCloud cloud; cloud.pts = samples;
typedef KDTreeSingleIndexAdaptor<L2_Simple_Adaptor<double, PointCloud>, 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<double> resultSet(1);
resultSet.init(&ret_index, &out_dist_sqr);
index.findNeighbors(resultSet, &query_pt[0]);
gridMat.at<int>(iy, ix) = cloud.pts[ret_index].value;
}
}
if (m_cancelRequested.load()) { reportProgress(-1, true); return false; }
reportProgress(3);
// 1. 提取所有参与计算的物理相值 (std::map 已自动按从小到大排序)
std::vector<int> uniqueValues;
for (const auto& pair : m_ColorList) {
uniqueValues.push_back(pair.first);
}
int actualNumClasses = static_cast<int>(uniqueValues.size());
// ================= 高斯概率平滑 =================
if (sigma > 0.1 && actualNumClasses > 0) {
std::vector<cv::Mat> 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<int>(y, x);
// 找到该物理相值对应的层索引
for (int i = 0; i < actualNumClasses; ++i) {
if (uniqueValues[i] == currentVal) {
probLayers[i].at<float>(y, x) = 1.0f;
break;
}
}
}
}
// 核大小计算
int radius = static_cast<int>(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<float>(y, x);
if (p > max_prob) {
max_prob = p;
best_idx = i;
}
}
// 重组写回真实的相值
gridMat.at<int>(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<float> 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<int>(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<CColorItem, CColorItem> 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;
}