From 201046a8b83f34ea099a11043ade28d8457d7af9 Mon Sep 17 00:00:00 2001
From: mrDarker <mr.darker@163.com>
Date: 星期六, 02 八月 2025 10:47:04 +0800
Subject: [PATCH] 1. 添加计算时间戳宏 2. 重写切割两片玻璃函数 3. 添加分组过滤干扰点的函数
---
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h | 18 +++
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp | 248 +++++++++++++++++++++++++++++++++++++------------
2 files changed, 200 insertions(+), 66 deletions(-)
diff --git a/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp b/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
index eb946f3..7529194 100644
--- a/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
+++ b/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
@@ -35,6 +35,19 @@
#define TIMER_INTERVAL_MS 500
#define TIMER_ID_OUTPUT_UPDATE 1
+// 璁℃椂瀹忓畾涔�
+#define MEASURE_FUNC_START() \
+ clock_t __startClock = clock();
+
+#define MEASURE_FUNC_END() \
+ do { \
+ clock_t __endClock = clock(); \
+ double __elapsedMs = 1000.0 * (__endClock - __startClock) / CLOCKS_PER_SEC; \
+ CString __strElapsed; \
+ __strElapsed.Format(_T("%s 鎵ц鑰楁椂锛�%.1f ms"), _T(__FUNCTION__), __elapsedMs); \
+ AppendLogLineRichStyled(__strElapsed, LOG_COLOR_SUCCESS); \
+ } while (0)
+
class CAboutDlg : public CDialogEx
{
public:
@@ -72,7 +85,7 @@
, m_dOutValues{ 0.0, 0.0, 0.0, 0.0 }
, m_nUseTrigger(0)
, m_nSavePointCount(100000)
- , m_fJumpThreshold(1.0f)
+ , m_fJumpThreshold(0.2f)
, m_nJumpWindow(3)
, m_nValleyMargin(0)
, m_nMinGlass1Count(10)
@@ -232,7 +245,7 @@
strFullLogLine.Format(_T("%s %s"), strLevel, strContent);
// 鍐欏叆鏃ュ織鏂囦欢
- LOG_LINE(strFullLogLine);
+ // LOG_LINE(strFullLogLine);
}
void CSGMeasurementDlg::HighlightAllMatches(const CString& strSearch, COLORREF clrHighlight/* = RGB(255, 165, 0)*/)
@@ -337,32 +350,38 @@
return false;
}
- RC nRet = SGIF_CloseDevice(DeviceID);
- if (nRet == RC_OK) {
- if (m_bSaving) {
- m_bSaving = FALSE;
- nRet = SGIF_DataStorageStop(DeviceID);
- if (nRet == RC_OK) {
- AppendLogLineRichStyled(_T("鏁版嵁瀛樺偍宸插仠姝€��"), LOG_COLOR_SUCCESS);
- }
- else {
- CString strError;
- strError.Format(_T("鍋滄鏁版嵁瀛樺偍澶辫触锛岄敊璇爜锛�%#X"), nRet);
- AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
- }
- }
+ // 鍋滄瀹氭椂鍣紝閬垮厤鍦ㄦ柇寮�鍚庝粛灏濊瘯鏇存柊杈撳嚭鏁版嵁
+ KillTimer(TIMER_ID_OUTPUT_UPDATE);
+ // 鍏堝仠姝㈡暟鎹瓨鍌紙濡傛灉姝e湪杩涜锛�
+ RC nRet;
+ if (m_bConnected && m_bSaving) {
+ nRet = SGIF_DataStorageStop(DeviceID);
+ if (nRet == RC_OK) {
+ m_bSaving = FALSE;
+ AppendLogLineRichStyled(_T("鏁版嵁瀛樺偍宸插仠姝€��"), LOG_COLOR_SUCCESS);
+ }
+ else {
+ CString strError;
+ strError.Format(_T("鍋滄鏁版嵁瀛樺偍澶辫触锛岄敊璇爜锛�%#X"), nRet);
+ AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
+ }
+ }
+
+ // 鏂紑璁惧杩炴帴
+ nRet = SGIF_CloseDevice(DeviceID);
+ if (nRet == RC_OK) {
AppendLogLineRichStyled(_T("鏂紑杩炴帴鎴愬姛锛�"), LOG_COLOR_SUCCESS);
m_bConnected = FALSE;
UpdateControlStatus(FALSE, FALSE);
- KillTimer(TIMER_ID_OUTPUT_UPDATE);
return true;
}
else {
CString strError;
strError.Format(_T("鏂紑杩炴帴澶辫触锛岄敊璇爜锛�%#X"), nRet);
AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
+ AppendLogLineRichStyled(_T("淇濇寔褰撳墠鐘舵�侊紝鏂紑澶辫触銆�"), LOG_COLOR_ERROR);
UpdateControlStatus(m_bConnected, m_bSaving);
return false;
@@ -371,6 +390,8 @@
void CSGMeasurementDlg::CleanInvalidValuesInPlace(int nOutNo, std::vector<float>& vecData, float fInvalid/* = -999.0f*/)
{
+ MEASURE_FUNC_START();
+
// 鎵惧埌绗竴涓湁鏁堝��
auto itStart = std::find_if(vecData.begin(), vecData.end(), [=](float v) { return v > fInvalid; });
@@ -406,62 +427,87 @@
else {
vecData.clear();
}
+
+ MEASURE_FUNC_END();
}
-bool CSGMeasurementDlg::SplitGlassSegments(int nOutNo, const std::vector<float>& vecData, std::vector<float>& vecGlass1, std::vector<float>& vecGlass2, float fJumpThreshold /*= 0.2f*/, int nWindow /*= 3*/, int nValleyMargin /*= 0*/, int nMinGlass1Count /*= 10*/)
+bool CSGMeasurementDlg::SplitGlassSegments(int nOutNo, const std::vector<float>& vecData, std::vector<float>& vecGlass1, std::vector<float>& vecGlass2, float fJumpThreshold /*= 0.2f*/, int nWindow /*= 3*/, int nValleyMargin /*= 0*/, int nMinGlassCount /*= 10*/)
{
- const int n = static_cast<int>(vecData.size());
- if (n < 2 * nWindow + 1 + nMinGlass1Count) {
- CString strError;
- strError.Format(_T("OUT%d: 鏁版嵁閲忎笉瓒筹紝鑷冲皯闇�瑕� %d 涓偣鎵嶈兘杩涜鍒囧壊銆�"), nOutNo, 2 * nWindow + 1 + nMinGlass1Count);
- AppendLogLineRichStyled(strError, LOG_COLOR_WARNING);
+ MEASURE_FUNC_START();
+
+ CString strLog;
+ const int nTotal = static_cast<int>(vecData.size());
+
+ if (nTotal < 2 * nWindow + 1 + nMinGlassCount) {
+ strLog.Format(_T("OUT%d: 鏁版嵁閲忎笉瓒筹紝鑷冲皯闇�瑕� %d 涓偣鎵嶈兘鍒囧壊銆�"), nOutNo, 2 * nWindow + 1 + nMinGlassCount);
+ AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
return false;
}
- int nValleyIdx = -1;
- for (int i = nWindow; i < n - nWindow - nMinGlass1Count; ++i) {
- float fDelta = std::fabs(vecData[i + nWindow] - vecData[i - nWindow]);
- if (fDelta > fJumpThreshold) {
- // 鎵� valley 鐐癸紙绋嶅井寰�鍚庤烦鍑犳浠ラ伩寮�杈圭紭鎶栧姩锛�
- int nSearchStart = i + nWindow + 2;
- int nSearchEnd = min(nSearchStart + 10, n);
+ // 浠庝腑闂村悜涓よ竟鏌ユ壘 valley 鍊欓�夌偣
+ int nMid = nTotal / 2;
+ int nValleyLeft = -1, nValleyRight = -1;
- auto itValley = std::min_element(vecData.begin() + nSearchStart, vecData.begin() + nSearchEnd,
- [](float a, float b) {
- return (a > -900.0f && b > -900.0f) ? a < b : false;
- });
-
- if (itValley != vecData.begin() + nSearchEnd) {
- nValleyIdx = static_cast<int>(std::distance(vecData.begin(), itValley));
- break;
- }
+ // 鍚戝乏鏌ユ壘 valley
+ for (int i = nMid; i >= nWindow; --i) {
+ float fDelta = std::fabs(vecData[i] - vecData[i - nWindow]);
+ if (fDelta > fJumpThreshold &&
+ vecData[i] < vecData[i - 1] &&
+ vecData[i] < vecData[i + 1]) {
+ nValleyLeft = i;
+ break;
}
}
- if (nValleyIdx < 0 || nValleyIdx < nMinGlass1Count) {
- AppendLogLineRichStyled(_T("鏈壘鍒板悎閫� valley 鐐癸紝鎴� valley 澶潬鍓嶃��"), LOG_COLOR_WARNING);
- return false;
+ // 鍚戝彸鏌ユ壘 valley
+ for (int i = nMid; i <= nTotal - nWindow - 2; ++i) {
+ float fDelta = std::fabs(vecData[i + nWindow] - vecData[i]);
+ if (fDelta > fJumpThreshold &&
+ vecData[i] < vecData[i - 1] &&
+ vecData[i] < vecData[i + 1]) {
+ nValleyRight = i;
+ break;
+ }
}
- // 浠� valley 澶勫紑濮嬪垏鍓诧紝鎴栬�呭線鍚庡亸绉�
- int nCutStart = min(nValleyIdx + nValleyMargin, n);
- vecGlass1.assign(vecData.begin(), vecData.begin() + nCutStart);
- vecGlass2.assign(vecData.begin() + nCutStart, vecData.end());
+ // 閫夊畾 valley 鐐�
+ int nValleyIdx = -1;
+ if (nValleyLeft > 0 && nValleyRight > 0) {
+ nValleyIdx = (vecData[nValleyLeft] < vecData[nValleyRight]) ? nValleyLeft : nValleyRight;
+ }
+ else if (nValleyLeft > 0) {
+ nValleyIdx = nValleyLeft;
+ }
+ else if (nValleyRight > 0) {
+ nValleyIdx = nValleyRight;
+ }
+
+ // fallback: valley 鏈壘鍒帮紝浣跨敤涓棿鍒囧壊娉�
+ if (nValleyIdx < 0) {
+ AppendLogLineRichStyled(_T("鏈壘鍒� valley 璺冲彉鐐癸紝浣跨敤涓棿浣嶇疆鍒囧壊銆�"), LOG_COLOR_WARNING);
+ nValleyIdx = nMid;
+ }
+
+ // 搴旂敤鍒囧壊浣嶇疆锛岄檺鍒惰竟鐣�
+ int nCutPos = max(1, min(nTotal - 1, nValleyIdx + nValleyMargin));
+
+ vecGlass1.assign(vecData.begin(), vecData.begin() + nCutPos);
+ vecGlass2.assign(vecData.begin() + nCutPos, vecData.end());
int nGlass1Count = static_cast<int>(vecGlass1.size());
int nGlass2Count = static_cast<int>(vecGlass2.size());
// 鏃ュ織杈撳嚭
- CString strLog;
- strLog.Format(_T("OUT%d: 鍒囧壊鎴愬姛锛岀涓�鐗囩幓鐠冩暟鎹噺 %d锛岀浜岀墖鐜荤拑鏁版嵁閲� %d銆�"), nOutNo, nGlass1Count, nGlass2Count);
+ strLog.Format(_T("OUT%d: 鍒囧壊鎴愬姛锛岀涓�鐗囩幓鐠� %d 鐐癸紝绗簩鐗囩幓鐠� %d 鐐广��"), nOutNo, nGlass1Count, nGlass2Count);
AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
- if (nGlass1Count < nMinGlass1Count) {
- strLog.Format(_T("OUT%d: 绗竴鐗囩幓鐠冩暟鎹噺杩囧皯锛屽彲鑳藉奖鍝嶅悗缁鐞嗐��"), nOutNo);
+ if (nGlass1Count < nMinGlassCount) {
+ strLog.Format(_T("OUT%d: 绗竴鐗囩幓鐠冩暟鎹噺灏戜簬 %d 鐐癸紝鍙兘褰卞搷璁$畻銆�"), nOutNo, nMinGlassCount);
AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
}
- if (nGlass2Count < nMinGlass1Count) {
- strLog.Format(_T("OUT%d: 绗簩鐗囩幓鐠冩暟鎹噺杩囧皯锛屽彲鑳藉奖鍝嶅悗缁鐞嗐��"), nOutNo);
+
+ if (nGlass2Count < nMinGlassCount) {
+ strLog.Format(_T("OUT%d: 绗簩鐗囩幓鐠冩暟鎹噺灏戜簬 %d 鐐癸紝鍙兘褰卞搷璁$畻銆�"), nOutNo, nMinGlassCount);
AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
}
@@ -471,11 +517,61 @@
AppendLogLineRichStyled(_T("绗簩鐗囩幓鐠冩暟鎹細"), LOG_COLOR_NORMAL);
PrintSampleData(nOutNo, vecGlass2);
- return !vecGlass1.empty() && !vecGlass2.empty();
+ MEASURE_FUNC_END();
+
+ return true;
+}
+
+bool CSGMeasurementDlg::FilterDominantGroup(int nOutNo, const std::vector<float>& vecInput, std::vector<float>& vecOutput)
+{
+ MEASURE_FUNC_START();
+
+ if (vecInput.empty()) {
+ AppendLogLineRichStyled(_T("杈撳叆鏁版嵁涓虹┖锛屾棤娉曡繘琛屽垎缁勭瓫閫夈��"), LOG_COLOR_WARNING);
+ return false;
+ }
+
+ // 鍒嗙粍锛歮ap<int鏁存暟閮ㄥ垎, vector<float>>
+ std::map<int, std::vector<float>> mapGroup;
+ for (float fVal : vecInput) {
+ int nKey = static_cast<int>(fVal);
+ mapGroup[nKey].push_back(fVal);
+ }
+
+ // 鎵惧嚭鏁伴噺鏈�澶氱殑閭g粍
+ size_t nMaxCount = 0;
+ auto itMaxGroup = mapGroup.begin();
+ for (auto it = mapGroup.begin(); it != mapGroup.end(); ++it) {
+ if (it->second.size() > nMaxCount) {
+ nMaxCount = it->second.size();
+ itMaxGroup = it;
+ }
+ }
+
+ if (nMaxCount == 0) {
+ AppendLogLineRichStyled(_T("鎵�鏈夋暟鎹兘琚繃婊ゆ垨涓虹┖锛屾棤娉曠瓫閫夊嚭涓昏鍒嗙粍銆�"), LOG_COLOR_WARNING);
+ return false;
+ }
+
+ vecOutput = itMaxGroup->second;
+
+ CString strLog;
+ strLog.Format(_T("鎴愬姛浠� %d 涓粍涓瓫閫夊嚭涓昏鍒嗙粍锛氬�肩害涓� %d锛屽叡 %zu 涓偣銆�"), static_cast<int>(mapGroup.size()), itMaxGroup->first, nMaxCount);
+ AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
+
+ // 鎵撳嵃绛涢�夊悗鐨勬暟鎹�
+ AppendLogLineRichStyled(_T("绛涢�夊悗鐨勪富瑕佸垎缁勬暟鎹細"), LOG_COLOR_NORMAL);
+ PrintSampleData(nOutNo, vecOutput);
+
+ MEASURE_FUNC_END();
+
+ return true;
}
bool CSGMeasurementDlg::ExtractStableRegionFixed(int nOutNo, const std::vector<float>& vecIn, std::vector<float>& vecOut, int nFixedCount/* = 5*/, float fMaxDelta/* = 0.04f*/)
{
+ MEASURE_FUNC_START();
+
const int n = static_cast<int>(vecIn.size());
if (n < nFixedCount) {
CString strError;
@@ -520,11 +616,15 @@
AppendLogLineRichStyled(_T("鎻愬彇鐨勭ǔ瀹氬尯鏁版嵁锛�"), LOG_COLOR_NORMAL);
PrintSampleData(nOutNo, vecOut);
+ MEASURE_FUNC_END();
+
return true;
}
bool CSGMeasurementDlg::CalcGlassOffset(const std::vector<float>& vecGlass1, const std::vector<float>& vecGlass2, float& fAvg1, float& fAvg2, float& fOffset)
{
+ MEASURE_FUNC_START();
+
if (vecGlass1.empty() || vecGlass2.empty()) {
AppendLogLineRichStyled(_T("绋冲畾鍖烘暟鎹负绌猴紝鏃犳硶璁$畻骞冲潎鍊煎拰鍋忕Щ銆�"), LOG_COLOR_WARNING);
return false;
@@ -542,6 +642,8 @@
CString strLog;
strLog.Format(_T("绗竴鐗囩幓鐠冨钩鍧囧��: %.3f锛岀浜岀墖鐜荤拑骞冲潎鍊�: %.3f锛屽亸绉婚噺: %.3f"), fAvg1, fAvg2, fOffset);
AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
+
+ MEASURE_FUNC_END();
return true;
}
@@ -612,6 +714,8 @@
float CSGMeasurementDlg::AnalyzeStoredData(int nOutNo)
{
+ MEASURE_FUNC_START();
+
UpdateData(TRUE);
if (m_nUseTrigger) {
@@ -640,8 +744,6 @@
return -1.0f;
}
- clock_t startClock = clock(); // 璁板綍寮�濮嬫椂闂�
-
std::vector<float> vecBuffer(m_nSavePointCount, 0.0f);
int nReceived = 0;
@@ -662,9 +764,29 @@
return -1.0f;
}
+ std::vector<float> vecGlass1Filtered, vecGlass2Filtered;
+ bool bGlass1Filtered = FilterDominantGroup(nOutNo, vecGlass1, vecGlass1Filtered);
+ bool bGlass2Filtered = FilterDominantGroup(nOutNo, vecGlass2, vecGlass2Filtered);
+
+ if (!bGlass1Filtered) {
+ AppendLogLineRichStyled(_T("Glass1 鍒嗘涓湭鑳借瘑鍒嚭涓绘暟鎹粍锛屼娇鐢ㄥ師濮嬫暟鎹��"), LOG_COLOR_WARNING);
+ vecGlass1Filtered = vecGlass1;
+ }
+ else {
+ AppendLogLineRichStyled(_T("Glass1 涓绘暟鎹粍宸叉彁鍙栥��"), LOG_COLOR_SUCCESS);
+ }
+
+ if (!bGlass2Filtered) {
+ AppendLogLineRichStyled(_T("Glass2 鍒嗘涓湭鑳借瘑鍒嚭涓绘暟鎹粍锛屼娇鐢ㄥ師濮嬫暟鎹��"), LOG_COLOR_WARNING);
+ vecGlass2Filtered = vecGlass2;
+ }
+ else {
+ AppendLogLineRichStyled(_T("Glass2 涓绘暟鎹粍宸叉彁鍙栥��"), LOG_COLOR_SUCCESS);
+ }
+
std::vector<float> vecStable1, vecStable2;
- bool bStable1 = ExtractStableRegionFixed(nOutNo, vecGlass1, vecStable1, m_nFixedCount, m_fMaxDelta);
- bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2, vecStable2, m_nFixedCount, m_fMaxDelta);
+ bool bStable1 = ExtractStableRegionFixed(nOutNo, vecGlass1Filtered, vecStable1, m_nFixedCount, m_fMaxDelta);
+ bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2Filtered, vecStable2, m_nFixedCount, m_fMaxDelta);
float fAvg1 = 0.0f, fAvg2 = 0.0f, fOffset = 0.0f;
if (bStable1 && bStable2) {
@@ -676,12 +798,7 @@
CalcGlassOffset(vecGlass1, vecGlass2, fAvg1, fAvg2, fOffset);
}
- clock_t endClock = clock(); // 璁板綍缁撴潫鏃堕棿
- double dElapsedMs = 1000.0 * (endClock - startClock) / CLOCKS_PER_SEC;
-
- CString strElapsed;
- strElapsed.Format(_T("AnalyzeStoredData 鎵ц鑰楁椂锛�%.1f ms"), dElapsedMs);
- AppendLogLineRichStyled(strElapsed, LOG_COLOR_SUCCESS);
+ MEASURE_FUNC_END();
return fOffset;
}
@@ -829,6 +946,11 @@
CString strError;
strError.Format(_T("鑾峰彇娴嬮噺鍊煎け璐ワ紝閿欒鐮侊細%#X"), nRet);
AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
+
+ // 鏂紑杩炴帴
+ if (m_bConnected) {
+ DisconnectFromDevice();
+ }
}
}
diff --git a/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h b/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h
index 3b8748c..53293f7 100644
--- a/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h
+++ b/SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h
@@ -3,6 +3,7 @@
//
#pragma once
+#include <map>
#include <vector>
#include <numeric>
#include <algorithm>
@@ -145,14 +146,25 @@
* @param fJumpThreshold 璺冲彉闃堝�硷紝鐢ㄤ簬妫�娴嬫暟鎹殑鏄庢樉鏂偣銆�
* @param nWindow 璺冲彉鍒ゆ柇鐨勫墠鍚庣獥鍙e搴︺��
* @param nValleyMargin valley 鐐逛箣鍚庡灏戜釜鐐逛綔涓哄疄闄呭垏鍓茬偣銆�
- * @param nMinGlass1Count 绗竴娈垫渶灏戞湁鏁堢偣鏁伴檺鍒躲��
+ * @param nMinGlass1Count 鏈�灏戞湁鏁堢偣鏁伴檺鍒躲��
*
* @return true 琛ㄧず鍒囧壊鎴愬姛锛宖alse 琛ㄧず澶辫触锛堜緥濡傛暟鎹笉瓒虫垨鏃犳槑鏄捐烦鍙橈級銆�
*/
bool SplitGlassSegments(int nOutNo, const std::vector<float>& validData,
std::vector<float>& vecGlass1, std::vector<float>& vecGlass2,
- float fJumpThreshold = 1.0f, int nWindow = 3, int nValleyMargin = 0,
- int nMinGlass1Count = 10);
+ float fJumpThreshold = 0.2f, int nWindow = 3, int nValleyMargin = 0,
+ int nMinGlassCount = 10);
+
+ /**
+ * @brief 瀵规暟鎹寜鏁存暟閮ㄥ垎杩涜鍒嗙粍锛屼繚鐣欐暟鎹噺鏈�澶氱殑涓�缁勶紙鎺掗櫎寮傚父/骞叉壈锛夈��
+ *
+ * @param nOutNo 杈撳嚭閫氶亾缂栧彿锛岀敤浜庢棩蹇楄褰曘��
+ * @param vecInput 鍘熷娴偣鏁版嵁锛堝凡瑁佸壀鏃犳晥鍊硷級銆�
+ * @param vecOutput 杈撳嚭琚繚鐣欑殑涓昏鍒嗙粍鏁版嵁锛堟渶澶氱殑閭g粍锛夈��
+ *
+ * @return true 琛ㄧず鎴愬姛杩囨护鍑轰富鍒嗙粍锛宖alse 琛ㄧず鎵�鏈夋暟鎹兘琚繃婊ゆ垨涓虹┖銆�
+ */
+ bool FilterDominantGroup(int nOutNo, const std::vector<float>& vecInput, std::vector<float>& vecOutput);
/**
* @brief 浠庤緭鍏ユ暟鎹腑鎻愬彇涓�涓浐瀹氶暱搴︾殑绋冲畾鍖洪棿銆�
--
Gitblit v1.9.3