|
|
#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;
|
|
|
} |