Browse Source

优化光学算法

master
zhaohe 2 months ago
parent
commit
c9fafaa239
  1. 12
      src/main/java/a8k/app/constant/OptConstant.java
  2. 29
      src/main/java/a8k/app/dao/AppStatePersistenceDao.java
  3. 7
      src/main/java/a8k/app/dao/type/db/OptCfg.java
  4. 1
      src/main/java/a8k/app/dao/type/db/OptRawScanData.java
  5. 25
      src/main/java/a8k/app/dao/type/db/ReactionReport.java
  6. 11
      src/main/java/a8k/app/factory/A8kOptProcessExceptionFactory.java
  7. 30
      src/main/java/a8k/app/i18n/Internationalization.java
  8. 269
      src/main/java/a8k/app/optalgo/A8kOptCurveAnalyzer.java
  9. 49
      src/main/java/a8k/app/optalgo/A8kPeakAnalyzer.java
  10. 106
      src/main/java/a8k/app/optalgo/algo/LinearRegressionCalculator.java
  11. 1
      src/main/java/a8k/app/optalgo/type/A8kOptProcessException.java
  12. 12
      src/main/java/a8k/app/optalgo/type/PeakPresetPosConfig.java
  13. 2
      src/main/java/a8k/app/optalgo/type/ReactionResultStatus.java
  14. 8
      src/main/java/a8k/app/service/data/ProjIdCardInfoMgrService.java
  15. 17
      src/main/java/a8k/app/service/data/ReactionRecordMgrService.java
  16. 47
      src/main/java/a8k/app/service/lowerctrl/OptScanModuleLowerCtrlService.java
  17. 2
      src/main/java/a8k/app/service/module/OptScanCtrlModule.java
  18. 18
      src/main/java/a8k/app/service/module/SamplePreProcessModule.java
  19. 11
      src/main/java/a8k/app/type/a8k/container/A8kOptPeakContainer.java
  20. 4
      src/main/java/a8k/app/type/a8k/opt/A8kOptPeak.java
  21. 1
      src/main/java/a8k/app/type/a8k/opt/PeakName.java
  22. 4
      src/main/java/a8k/app/type/a8k/optfn/A8kOptX.java
  23. 13
      src/main/java/a8k/app/type/a8k/state/SampleInfo.java
  24. 6
      src/main/java/a8k/extui/mgr/ExtApiPageGroupCfgMgr.java
  25. 70
      src/main/java/a8k/extui/page/optalgotest/OptAlgoTestPage.java
  26. 3
      src/main/java/a8k/extui/page/test/codetest/OptFormulaTestPageV2.java
  27. 76
      src/main/resources/db/zapp_a8k_project_opt_config.csv
  28. BIN
      tools/20250427-F-光学光学报告.xlsx
  29. BIN
      tools/20250427-T-光学光学报告-2.xlsx

12
src/main/java/a8k/app/constant/OptConstant.java

@ -1,8 +1,12 @@
package a8k.app.constant;
public class OptConstant {
static public final Integer OPT_F_RESULT_TOLERATE = 300;
static public final Integer OPT_T_RESULT_TOLERATE = 700;
static public final Integer OPT_T_TARGET_VAL = 2300;
static public final Integer OPT_F_TARGET_VAL = 3600;
static public final Integer OPT_T_RESULT_TOLERATE = 700;
static public final Integer OPT_T_TARGET_VAL = 2300;
static public final Integer OPT_T_FINAL_TARGET_VAL = 3600;
static public final Integer OPT_F_TARGET_VAL = 3600;
static public final Integer OPT_F_RESULT_TOLERATE = 300;
static public final Integer OPT_F_FINAL_TARGET_VAL = 3600;
static public final Integer OPT_F_OVERFLOW_VAL = 3900;
}

29
src/main/java/a8k/app/dao/AppStatePersistenceDao.java

@ -0,0 +1,29 @@
package a8k.app.dao;
import a8k.app.dao.type.db.KeyVal;
import a8k.app.utils.ZSqlite;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class AppStatePersistenceDao extends ZSqlite<KeyVal> {
@Resource
JdbcTemplate jdbcTemplate;
@PostConstruct
void init() {
init(jdbcTemplate, "zapp_state_persistence_dao", KeyVal.class, false);
}
void storage(String key, String val) {
if (key == null || val == null) {
log.error("key or val is null, key: {}, val: {}", key, val);
return;
}
}
}

7
src/main/java/a8k/app/dao/type/db/OptCfg.java

@ -1,8 +1,10 @@
package a8k.app.dao.type.db;
import a8k.app.type.a8k.opt.A8kOptType;
import a8k.app.type.a8k.opt.PeakName;
import java.io.Serializable;
import java.util.List;
public class OptCfg implements Serializable {
public int id = 0;
@ -17,6 +19,7 @@ public class OptCfg implements Serializable {
public A8kOptType optType; //子项目 光学类型 necessity
public Integer optScanRange; //子项目 扫描范围 扫描范围
public Integer optScanPeakNum; //子项目 实际扫描到的峰的数量
public Integer peakNameRefNum; //子项目 峰的数量
public Integer optScanPeakNum; //子项目 实际扫描到的峰的数量
public Integer peakNameRefNum; //子项目 峰的数量
public List<PeakName> peakConfig;
}

1
src/main/java/a8k/app/dao/type/db/OptRawScanData.java

@ -8,6 +8,7 @@ public class OptRawScanData {
public Integer lasterGain;
public Integer scanGain;
public Integer[] rawData;
public Boolean overflow;
public String toString() {
return ZJsonHelper.objectToJson(this);

25
src/main/java/a8k/app/dao/type/db/ReactionReport.java

@ -6,6 +6,7 @@ import a8k.app.type.a8k.optfn.A8kResultUnitConverterFn;
import a8k.app.type.a8k.BloodType;
import a8k.app.type.a8k.opt.A8kOptType;
import a8k.app.type.a8k.proj.ProjInfo;
import a8k.app.type.a8k.state.SampleInfo;
import a8k.app.utils.ZJsonHelper;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
@ -81,13 +82,15 @@ public class ReactionReport implements Serializable { //记录单个反应板的
public Date creatDate = new Date();
@Schema(description = "血液类型")
public BloodType sampleBloodType = BloodType.WHOLE_BLOOD; //血液类型
public BloodType sampleBloodType = BloodType.WHOLE_BLOOD; //血液类型
@Schema(description = "样本条码")
public String sampleBarcode = ""; //用于请求用户信息的条码ID
public String sampleBarcode = ""; //用于请求用户信息的条码ID
@Schema(description = "样本USRID(不做逻辑,只做展示)")
public String sampleUserid = ""; //用户输入的样本ID不做逻辑只做展示
public String sampleUserid = ""; //用户输入的样本ID不做逻辑只做展示
@Schema(description = "样本ID(系统生成用于追溯)")
public String sampleId = ""; // 样本ID,由系统生成
public String sampleId = ""; // 样本ID,由系统生成
// @Schema(description = "是否是急诊", example = "false")
// public Boolean sampleIsEmergency = false; // 是否急诊
//项目信息
@Schema(description = "项目名称", example = "Tn-I/CK-MB/Myoglobin")
@ -130,6 +133,8 @@ public class ReactionReport implements Serializable { //记录单个反应板的
@JsonIgnore
public List<OptScanResult> detailOptData = new ArrayList<>();// 扫描上下文数据(包含原始结果扫描参数扫描原始数据)
// public ProjExtInfoCard projExtInfoCard;
public OptScanResult getOptData(int subProjIndex) {
A8kOptType subProjOptType = getSubProjOptType(subProjIndex);
@ -150,5 +155,17 @@ public class ReactionReport implements Serializable { //记录单个反应板的
return projInfo.buildIn.optcfg.get(subProjIndex).subProjName;
}
@JsonIgnore
public SampleInfo getSampleInfo() {
return new SampleInfo(
sampleId,
false,
false,//在最终结果中添加急诊标识位
sampleBloodType,
sampleUserid,
sampleId
);
}
}

11
src/main/java/a8k/app/factory/A8kOptProcessExceptionFactory.java

@ -0,0 +1,11 @@
package a8k.app.factory;
import a8k.app.i18n.Internationalization;
import a8k.app.optalgo.type.A8kOptProcessException;
import a8k.app.optalgo.type.ReactionResultStatus;
public class A8kOptProcessExceptionFactory {
static public A8kOptProcessException create(ReactionResultStatus status) {
return new A8kOptProcessException(status, Internationalization.reactionResultStatus2String(status));
}
}

30
src/main/java/a8k/app/i18n/Internationalization.java

@ -1,5 +1,6 @@
package a8k.app.i18n;
import a8k.app.optalgo.type.ReactionResultStatus;
import a8k.app.type.a8k.BloodType;
import a8k.app.type.error.ConsumablesScanReportErrorType;
import a8k.app.hardware.type.A8kEcode;
@ -120,4 +121,33 @@ public class Internationalization {
case FECES -> "粪便";
};
}
public static String reactionResultStatus2String(ReactionResultStatus status) {
return switch (status) {
case DISABLED -> "未启用";
case SUCCESS -> "成功";
case ERROR -> "结果异常";
case ERROR_RESULT_OUT_OF_RANGE -> "结果超出范围";
case ERROR_RESULT_EXCEED_THE_UPPER_LIMIT -> "超过上限";
case ERROR_RESULT_BELOW_THE_LOWER_LIMIT -> "低于下限";
case ERROR_X_EXCEED_THE_UPPER_LIMIT -> "超过上限";
case ERROR_X_BELOW_THE_LOWER_LIMIT -> "低于下限";
case ERROR_QUALITY_PEAK_DETECTION -> "质峰检测错误";
case ERROR_LOST_PEAK_T4 -> "T4峰丢失";
case ERROR_LOST_PEAK_R -> "R峰丢失";
case ERROR_LOST_PEAK_H -> "H峰丢失";
case ERROR_LOST_PEAK_T -> "T峰丢失";
case ERROR_LOST_PEAK_C -> "C峰丢失";
case ERROR_COMPUTE_R_FAIL -> "计算R失败";
case ERROR_COMPUTE_AR_FAIL -> "计算AR失败";
case ERROR_COMPUTE_ATR_FAIL -> "计算ATR失败";
case ERROR_COMPUTE_RFR_FAIL -> "计算RFR失败";
case ERROR_COMPUTE_T4R_FAIL -> "计算T4R失败";
case ERROR_COMPUTE_T4T3R_FAIL -> "计算T4T3R失败";
case ERROR_UNKNOWN_X -> "未知X";
};
}
}

269
src/main/java/a8k/app/optalgo/A8kOptCurveAnalyzer.java

@ -1,17 +1,23 @@
package a8k.app.optalgo;
import a8k.app.dao.type.db.OptCfg;
import a8k.app.optalgo.algo.LeastSquare;
import a8k.app.optalgo.algo.LinearRegressionCalculator;
import a8k.app.optalgo.algo.SubSampling;
import a8k.app.optalgo.algo.SupperSampling;
import a8k.app.optalgo.type.*;
import a8k.app.dao.type.db.OptCfg;
import a8k.app.optalgo.type.A8kOptPeakInfo;
import a8k.app.optalgo.type.PeakPresetPosConfig;
import a8k.app.optalgo.version.OptAlogVersion;
import a8k.app.type.a8k.container.A8kOptPeakContainer;
import a8k.app.type.a8k.opt.A8kOptPeak;
import a8k.app.type.a8k.opt.PeakName;
import a8k.app.type.a8k.opt.PeakQuotient;
import a8k.app.utils.ZJsonHelper;
import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import java.util.List;
@Slf4j
@ -28,8 +34,27 @@ public class A8kOptCurveAnalyzer {
OptCfg optCfg;
Integer projId;
public static void assignPeakName(OptCfg optCfg, A8kOptPeakContainer peaks) {
Assert.notNull(optCfg, "OptCfg cannot be null");
Assert.notNull(peaks, "A8kOptPeakContainer cannot be null");
Assert.isTrue(optCfg.peakConfig != null && !optCfg.peakConfig.isEmpty(), "Peak configuration cannot be null or empty");
Assert.isTrue(optCfg.peakConfig.size() == 5, "Peak configuration must contain exactly 5 peaks");
List<PeakName> peakNames = optCfg.peakConfig;
for (int i = 0; i < peakNames.size(); i++) {
peaks.getPeak(i).peakName = peakNames.get(i);
}
}
public A8kOptPeakInfo analysisCurve(Integer projId, OptCfg optCfg, Integer[] data) {
Assert.isTrue(optCfg.peakConfig != null && !optCfg.peakConfig.isEmpty(), "Peak configuration cannot be null or empty");
Assert.isTrue(optCfg.peakConfig.size() == 5, "Peak configuration must contain exactly 5 peaks");
for (PeakName peakName : optCfg.peakConfig) {
Assert.notNull(peakName, "Peak name cannot be null");
}
public A8kOptPeakInfo analysCurve(Integer projId, OptCfg optCfg, Integer[] data) {
double[] doubleData = new double[data.length];
for (int i = 0; i < data.length; i++) {
doubleData[i] = data[i];
@ -37,61 +62,9 @@ public class A8kOptCurveAnalyzer {
this.optCfg = optCfg;
this.rawData = data;
this.projId = projId;
return analysCurve(doubleData);
}
public static void assignPeakName(Integer projId, Integer peakNameRefNum, A8kOptPeakContainer peaks) {
//项目ID==1时峰的命名是特殊的
if (projId == 2) {
peaks.P080.peakName = PeakName.C;
peaks.P120.peakName = PeakName.T;
} else {
//
switch (peakNameRefNum) {
case 2 -> {
peaks.P080.peakName = PeakName.T;
peaks.P120.peakName = PeakName.C;
}
case 3 -> {
peaks.P040.peakName = PeakName.H;
peaks.P080.peakName = PeakName.T;
peaks.P120.peakName = PeakName.C;
}
case 4 -> {
peaks.P040.peakName = PeakName.R;
peaks.P080.peakName = PeakName.H;
peaks.P120.peakName = PeakName.T;
peaks.P160.peakName = PeakName.C;
}
case 5 -> {
peaks.P040.peakName = PeakName.T4;
peaks.P080.peakName = PeakName.R;
peaks.P120.peakName = PeakName.H;
peaks.P160.peakName = PeakName.T;
peaks.P200.peakName = PeakName.C;
}
default -> {
}
}
}
}
public static void main(String[] args) {
A8kOptPeakContainer peaks = new A8kOptPeakContainer();
assignPeakName(1, 2, peaks);
log.info("{}",
ZJsonHelper.objToPrettyJson(peaks));
}
private A8kOptPeakInfo analysCurve(double[] data) {
//过采样
afSupperVal = SupperSampling.process(data, 5);
afSupperVal = SupperSampling.process(doubleData, 5);
afSubSampling = SubSampling.process(afSupperVal, 24);
avg = afSubSampling;
@ -99,22 +72,62 @@ public class A8kOptCurveAnalyzer {
diffx2 = LeastSquare.calculatedSlopeCurve(diff, 5);
avgLine = findAvgLine(afSubSampling);
List<PeakName> peakNames = optCfg.peakConfig;
A8kOptPeakContainer peaks = new A8kOptPeakContainer();
A8kOptPeakContainer peaks = new A8kOptPeakContainer();
// assign peak names to peaks
for (int i = 0; i < peakNames.size(); i++) {
peaks.getPeak(i).peakName = peakNames.get(i);
}
for (int i = 0; i < peakNames.size(); i++) {
tryFindPeak(afSubSampling, peaks, i);
}
//计算峰的面积
for (int i = 0; i < 5; i++) {
A8kOptPeak peak = peaks.getPeak(i);
if (peak.isFindPeak()) {
peak.area = computePeakArea(afSubSampling, peak.peakStartPos, peak.peakEndPos);
}
}
return buildA8kOptPeakInfo(afSubSampling, peaks);
findpeak(avg, 20, 60, peaks.P040);
findpeak(avg, 60, 100, peaks.P080);
findpeak(avg, 100, 140, peaks.P120);
findpeak(avg, 140, 180, peaks.P160);
findpeak(avg, 180, 220, peaks.P200);
}
assignPeakName(projId, optCfg.peakNameRefNum, peaks);
void tryFindPeak(double[] data, A8kOptPeakContainer peaks, Integer off) {
A8kOptPeak curPeak = peaks.getPeak(off);
A8kOptPeak prePeak = off == 0 ? null : peaks.getPeak(off - 1);
if (!curPeak.peakName.equals(PeakName.N)) {
findPeak(avg, (off + 1) * 40, 40, curPeak);
return buildA8kOptPeakInfo(afSubSampling, peaks);
if (curPeak.isFindPeak()) {
if (prePeak == null || prePeak.peakName.equals(PeakName.N) || !prePeak.isFindPeak()) {
curPeak.peakStartPos = curPeak.peakPos - 20; // 20个点的中间位置
} else {
curPeak.peakStartPos = (prePeak.peakPos + curPeak.peakPos) / 2;
//修正上一个Peak的结束位置
prePeak.peakEndPos = curPeak.peakStartPos;
}
//Peak的结束位先设置为当前Peak的开始位 + 20个点
curPeak.peakEndPos = curPeak.peakPos + 20; // 20个点的中间位置
}
}
}
double findAvgLine(double[] inputRaw) {
/*
* 下面代码是为了计算平均线
* 条件cnt < (range - 15 * inputRaw.length / 250) 是为了过滤掉曲线中的峰值.
*
* 逻辑如下
* 计算所有小于 base_min 的点的平均值
* 如果小于base_min的点数过少, 说明base_min过低, 提高base_min, 重新计算.
* 直到小于base_min的点数满足条件, 计算出平均值.
*
*/
double base_min = 500;
double fsum = 0;
int cnt = 0;
@ -131,74 +144,85 @@ public class A8kOptCurveAnalyzer {
}
base_min = base_min + 50;
} while (cnt < range - 15 * inputRaw.length / 250);
} while (cnt < (range - 15 * inputRaw.length / 250));
return fsum / cnt;
}
void findpeak(double[] data, int search_start, int search_end, A8kOptPeak retpeak) {
// find peak
int findpeakTargetPos = search_start + (search_end - search_start) / 2;
log.info("find peak {}", findpeakTargetPos);
void findPeak(double[] data, int search_center, int range, A8kOptPeak retpeak) {
/*
*
* ....
* / \
* | |
* -------| |--------
*
*
*/
int search_start = search_center - range / 2;
int search_end = search_center + range / 2;
boolean checkPeakPassed = true;
retpeak.state = A8kOptPeak.State.NOT_FIND_PEAK;
retpeak.area = 0.0;
retpeak.peakPos = 0;
retpeak.peakStartPos = 0;
retpeak.peakEndPos = 0;
double max = 0;
int peakpos = 0;
int midpos = search_start + (search_end - search_start) / 2;
for (int i = search_start; i < search_end; i++) {
if (data[i] > max) {
max = data[i];
peakpos = i;
double max = -1;
double peakposval = 0;
int peakpos = -1;
log.info("------> find peak-{}", search_center);
for (int i = search_start; i < search_end - 20; i++) {
double sum = computeSum(data, i, 20);
if (sum > max) {
max = sum;
peakpos = i + 10; // 20个点的中间位置
peakposval = data[i + 10]; // 20个点的中间位置的值
}
}
if (max < avgLine) {
log.warn("find peak-{} fail, max:{} < m_cxt.agvline:{}", findpeakTargetPos, max, avgLine);
return;
} else if (peakpos > midpos + 15) {
log.warn("find peak-{} fail, peakpos > midpos + 15:{}", findpeakTargetPos, midpos + 15);
return;
} else if (peakpos < midpos - 15) {
log.warn("find peak-{} fail, peakpos < midpos - 15:{}", findpeakTargetPos, midpos - 15);
return;
log.info("- peakPos :{}", peakpos);
log.info("- peakDiffPo :{}", peakpos - search_center);
log.info("- peakValue :{}", peakposval);
log.info("-");
log.info("------> check it");
log.info("- check peakPosVal");
if (peakposval < avgLine) {
log.warn("- check peakPosVal < avgLine fail, peakPosVal={} avgLine={}", peakposval, avgLine);
checkPeakPassed = false;
} else {
log.info("- check peakPosVal < avgLine pass, peakPosVal={} avgLine={}", peakposval, avgLine);
}
log.info("-");
log.info("- check linear regression");
double[] result = LinearRegressionCalculator.calculateRegression(ArrayUtil.sub(data, search_start, search_end));
if (result[2] > 0.8) {
/*
* 这里对曲线做线性回归,如果线性回归通过,说明当前曲线是一条直线,说明当前曲线没有峰值
*/
log.warn("- check linear regression fail, rVal = {}", result[2]);
checkPeakPassed = false;
} else {
log.info("- check linear regression pass, rVal = {}", result[2]);
}
log.info("-");
// find_peak_start
// 从pos向前找20个点从低于均值线的坐标开始找找到diff2的最大值
retpeak.peakPos = peakpos;
retpeak.peakStartPos = findPeakStartTurnPoint(data, peakpos - 20, peakpos) - 4; //-4 是经验数值
retpeak.peakEndPos = findPeakEndTurnPoint(data, peakpos, peakpos + 20) + 4; //+4 是经验数值
retpeak.area = computePeakArea(data, retpeak.peakStartPos, retpeak.peakEndPos);
retpeak.state = A8kOptPeak.State.FIND_PEAK;
log.info("find peak:{} startpos:{} endpos:{} area:{}", retpeak.peakPos, retpeak.peakStartPos, retpeak.peakEndPos, retpeak.area);
// find_peak_end
// 从pos向后找20个点找到diff2的最大值
}
int findPeakStartTurnPoint(double[] data, int search_start, int suggest_search_end) {
int peakTurnPos = search_start;
double maxdiff2 = diffx2[search_start];
for (int i = search_start; i < suggest_search_end; i++) {
if (diffx2[i] > maxdiff2) {
maxdiff2 = diffx2[i];
peakTurnPos = i;
}
if (!checkPeakPassed) {
log.error("find peak-{} fail", search_center);
log.info("-");
retpeak.state = A8kOptPeak.State.NOT_FIND_PEAK;
return;
}
return peakTurnPos;
}
int findPeakEndTurnPoint(double[] data, int search_start, int suggest_search_end) {
return findPeakStartTurnPoint(new double[0], search_start, suggest_search_end);
retpeak.peakPos = peakpos;
retpeak.state = A8kOptPeak.State.FIND_PEAK;
log.info("- find peak-{} success, peakPos:{}", search_center, peakpos);
log.info("-");
}
double abs(double a) {
return a > 0 ? a : -a;
}
double computePeakArea(double[] data, int start, int end) {
double area = 0;
@ -207,7 +231,24 @@ public class A8kOptCurveAnalyzer {
}
double baselinearea = (data[start] + data[end]) * abs(end - start) / 2;
return abs(area - baselinearea);
area = area - baselinearea;
if (area < 0) {
area = 0;
}
return area;
}
double abs(double a) {
return a > 0 ? a : -a;
}
double computeSum(double[] data, int start, int windowSize) {
double sum = 0.0;
for (int i = start; i < start + windowSize && i < data.length; i++) {
sum += data[i];
}
return sum;
}
static private Double computePeakDivision(PeakQuotient pdtype, A8kOptPeakContainer peaks) {

49
src/main/java/a8k/app/optalgo/A8kPeakAnalyzer.java

@ -3,6 +3,7 @@ package a8k.app.optalgo;
import a8k.app.dao.type.db.OptCfg;
import a8k.app.dao.type.db.ProjExtInfoCard;
import a8k.app.dao.type.db.ReactionReport;
import a8k.app.factory.A8kOptProcessExceptionFactory;
import a8k.app.optalgo.type.A8kOptPeakInfo;
import a8k.app.optalgo.type.A8kOptProcessException;
import a8k.app.type.a8k.opt.PeakQuotient;
@ -64,25 +65,25 @@ public class A8kPeakAnalyzer {
case T4R -> ReactionResultStatus.ERROR_COMPUTE_T4R_FAIL;
case T4T3R -> ReactionResultStatus.ERROR_COMPUTE_T4T3R_FAIL;
};
throw new A8kOptProcessException(estatus, estatus.name());
throw A8kOptProcessExceptionFactory.create(estatus);
}
public static void checkX(Double x, Double lowLimit, Double upLimit) throws A8kOptProcessException {
if (x > upLimit) {
throw new A8kOptProcessException(ReactionResultStatus.ERROR_X_EXCEED_THE_UPPER_LIMIT, "ERROR_X_OUT_OF_RANGE");
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_X_EXCEED_THE_UPPER_LIMIT);
}
if (x < lowLimit) {
throw new A8kOptProcessException(ReactionResultStatus.ERROR_X_BELOW_THE_LOWER_LIMIT, "ERROR_X_OUT_OF_RANGE");
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_X_BELOW_THE_LOWER_LIMIT);
}
}
static public void checkResult1(Double result1, Double lowLimit, Double upLimit) throws A8kOptProcessException {
if (result1 > upLimit) {
throw new A8kOptProcessException(ReactionResultStatus.ERROR_RESULT_EXCEED_THE_UPPER_LIMIT, "ERROR_BEYOND_THE_UPPER_LIMIT");
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_RESULT_EXCEED_THE_UPPER_LIMIT);
}
if (result1 < lowLimit) {
throw new A8kOptProcessException(ReactionResultStatus.ERROR_RESULT_BELOW_THE_LOWER_LIMIT, "ERROR_BEYOND_THE_LOWER_LIMIT");
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_RESULT_BELOW_THE_LOWER_LIMIT);
}
}
@ -114,7 +115,10 @@ public class A8kPeakAnalyzer {
case T4R -> addPD(cxt, PeakQuotient.T4R);
case T4T3R -> addPD(cxt, PeakQuotient.T4T3R);
case RFR_ADD_T4T3R -> addPD(cxt, PeakQuotient.RFR, PeakQuotient.T4T3R);
default -> throw new A8kOptProcessException(ReactionResultStatus.ERROR_UNKOWN_X, "ERROR_UNKOWN_X");
case UNSUPPORTED -> {
zloggerRecorder.error("Unsupported X type: %s", xType);
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_UNKNOWN_X);
}
};
}
@ -290,10 +294,43 @@ public class A8kPeakAnalyzer {
return reactionResult;
}
static void checkPeakIsAllFind(OptCfg optCfg, A8kOptPeakInfo a8kOptPeakInfo) throws A8kOptProcessException {
for (var peak : optCfg.peakConfig) {
switch (peak) {
case T4 -> {
if (!a8kOptPeakInfo.T4.isFindPeak())
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_LOST_PEAK_T4);
}
case R -> {
if (!a8kOptPeakInfo.R.isFindPeak())
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_LOST_PEAK_R);
}
case H -> {
if (!a8kOptPeakInfo.H.isFindPeak())
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_LOST_PEAK_H);
}
case T -> {
if (!a8kOptPeakInfo.T.isFindPeak())
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_LOST_PEAK_T);
}
case C -> {
if (!a8kOptPeakInfo.C.isFindPeak())
throw A8kOptProcessExceptionFactory.create(ReactionResultStatus.ERROR_LOST_PEAK_C);
}
case N -> {
continue;
}
}
}
}
static private ReactionReport.ReactionResult analyzeResult(OptAnalyzeContext optcxt, A8kOptPeakInfo a8kOptPeakInfo) throws A8kOptProcessException {
// A8kOptFnFormula fnFormual = optcxt.projInfoCxt.getA8kOptFnFormula(optcxt.subProjIndex);
ProjExtInfoCard.A8kOptFnGroup fnFormual = ProjInfoUtils.getA8kOptFnFormula(optcxt.projInfoCxt.ext, optcxt.subProjIndex);
Assert.isTrue(fnFormual != null, "fnFormual must not be null");
checkPeakIsAllFind(optcxt.getProjOptInfo(), a8kOptPeakInfo);
Double result1 = null;
//
//粪便类型的样本使用,血清和血浆的参数

106
src/main/java/a8k/app/optalgo/algo/LinearRegressionCalculator.java

@ -0,0 +1,106 @@
package a8k.app.optalgo.algo;
import lombok.Data;
import java.util.List;
public class LinearRegressionCalculator {
@Data
public static class Point2D {
public double x;
public double y;
public Point2D(double x, double y) {
this.x = x;
this.y = y;
}
}
public static double[] calculateRegression(double[] points) {
List<Point2D> pointList = new java.util.ArrayList<>();
for (int i = 0; i < points.length; i += 1) {
pointList.add(new Point2D(i, points[i]));
}
return calculateRegression(pointList);
}
/**
* 计算线性回归和相关系数
* @param points 数据点集合
* @return 包含斜率截距和相关系数的数组 [slope, intercept, r]
*/
public static double[] calculateRegression(List<Point2D> points) {
int n = points.size();
if (n < 2) {
throw new IllegalArgumentException("至少需要两个点进行计算");
}
// 计算x和y的总和
double sumX = 0, sumY = 0;
for (Point2D p : points) {
sumX += p.getX();
sumY += p.getY();
}
// 计算x和y的平均值
double meanX = sumX / n;
double meanY = sumY / n;
// 计算所需的和
double sumXY = 0, sumXSq = 0, sumYSq = 0;
for (Point2D p : points) {
double xDev = p.getX() - meanX;
double yDev = p.getY() - meanY;
sumXY += xDev * yDev;
sumXSq += xDev * xDev;
sumYSq += yDev * yDev;
}
// 计算斜率 (m) 和截距 (b)
double slope = sumXY / sumXSq;
double intercept = meanY - slope * meanX;
// 计算相关系数 r
double r = sumXY / Math.sqrt(sumXSq * sumYSq);
return new double[]{slope, intercept, r};
}
public static void main(String[] args) {
// 示例数据点
List<Point2D> points = List.of(
new Point2D(1, 2),
new Point2D(2, 3),
new Point2D(3, 5),
new Point2D(4, 4),
new Point2D(5, 6)
);
// 计算线性回归和相关系数
double[] result = calculateRegression(points);
double slope = result[0];
double intercept = result[1];
double r = result[2];
// 输出结果
System.out.println("回归方程: y = " + String.format("%.4f", slope) + "x + " + String.format("%.4f", intercept));
System.out.println("相关系数 r = " + String.format("%.4f", r));
System.out.println("决定系数 R² = " + String.format("%.4f", r * r));
// 解释相关系数
String interpretation;
if (Math.abs(r) >= 0.8) {
interpretation = "强相关";
} else if (Math.abs(r) >= 0.5) {
interpretation = "中度相关";
} else {
interpretation = "弱相关";
}
System.out.println("相关性: " + interpretation);
}
}

1
src/main/java/a8k/app/optalgo/type/A8kOptProcessException.java

@ -7,5 +7,6 @@ public class A8kOptProcessException extends Exception {
public A8kOptProcessException(ReactionResultStatus status, String errorMsg) {
super(status.toString());
this.status = status;
this.errorMsg = errorMsg;
}
}

12
src/main/java/a8k/app/optalgo/type/PeakPresetPosConfig.java

@ -0,0 +1,12 @@
package a8k.app.optalgo.type;
import a8k.app.type.a8k.opt.PeakName;
import java.util.List;
public class PeakPresetPosConfig {
public PeakName P040 = null;
public PeakName P080 = null;
public PeakName P120 = null;
public PeakName P160 = null;
public PeakName P200 = null;
}

2
src/main/java/a8k/app/optalgo/type/ReactionResultStatus.java

@ -48,5 +48,5 @@ public enum ReactionResultStatus {
ERROR_COMPUTE_T4T3R_FAIL, //
ERROR_UNKOWN_X,
ERROR_UNKNOWN_X,
}

8
src/main/java/a8k/app/service/data/ProjIdCardInfoMgrService.java

@ -163,7 +163,7 @@ public class ProjIdCardInfoMgrService {
ecode.add(A8kEcode.PROJ_CARD_ERROR_WRONG_FUNCTION_X_TYPE);
}
if (formula.x != null && formula.x.equals(A8kOptX.USUPPORT)) {
if (formula.x != null && formula.x.equals(A8kOptX.UNSUPPORTED)) {
ecode.add(A8kEcode.PROJ_CARD_ERROR_WRONG_FUNCTION_X_TYPE);
}
@ -178,15 +178,15 @@ public class ProjIdCardInfoMgrService {
private void verifyA8kPiecewiseFn(A8kPiecewiseFn formula, List<A8kEcode> ecode) {
if (formula.judeX == null || formula.judeX.equals(A8kOptX.USUPPORT)) {
if (formula.judeX == null || formula.judeX.equals(A8kOptX.UNSUPPORTED)) {
ecode.add(A8kEcode.PROJ_CARD_ERROR_WRONG_FUNCTION_JUDGE_X_TYPE);
}
if (formula.lX == null || formula.lX.equals(A8kOptX.USUPPORT)) {
if (formula.lX == null || formula.lX.equals(A8kOptX.UNSUPPORTED)) {
ecode.add(A8kEcode.PROJ_CARD_ERROR_WRONG_FUNCTION_L_X_TYPE);
}
if (formula.hX == null || formula.hX.equals(A8kOptX.USUPPORT)) {
if (formula.hX == null || formula.hX.equals(A8kOptX.UNSUPPORTED)) {
ecode.add(A8kEcode.PROJ_CARD_ERROR_WRONG_FUNCTION_H_X_TYPE);
}

17
src/main/java/a8k/app/service/data/ReactionRecordMgrService.java

@ -36,7 +36,6 @@ public class ReactionRecordMgrService {
}
public void addRecord(SampleInfo sampleInfo, ProjInfo projInfo, List<OptScanResult> optScanResults, List<ReactionReport.ReactionResult> reactionResults) {
if (reactionResults == null || reactionResults.isEmpty()) {
return;
@ -45,13 +44,15 @@ public class ReactionRecordMgrService {
String operator = appUserMgrService.getLoginUsr() != null ? appUserMgrService.getLoginUsr().account : "UNLOGIN";
record.sampleBloodType = sampleInfo.bloodType;
record.sampleBarcode = sampleInfo.sampleBarcode;
record.sampleUserid = sampleInfo.userid;
record.sampleId = sampleInfo.sampleId;
record.projName = projInfo.buildIn.projName;
record.lotId = projInfo.ext.lotId;
record.projId = projInfo.ext.projId;
record.sampleBloodType = sampleInfo.bloodType;
record.sampleBarcode = sampleInfo.sampleBarcode;
record.sampleUserid = sampleInfo.userid;
record.sampleId = sampleInfo.sampleId;
// record.sampleIsEmergency = sampleInfo.isEmergency;
record.projName = projInfo.buildIn.projName;
record.lotId = projInfo.ext.lotId;
record.projId = projInfo.ext.projId;
// record.projExtInfoCard = projInfo.ext;//TODO:添加配置 支持保存和不保存ID卡信息
record.setExpiryDate(projInfo.ext.expiryDate);
record.operator = operator;
record.projShortName = projInfo.ext.projShortName;

47
src/main/java/a8k/app/service/lowerctrl/OptScanModuleLowerCtrlService.java

@ -37,9 +37,11 @@ public class OptScanModuleLowerCtrlService {
public Boolean scanAgain;
public Integer maxval;
public void process(Integer[] data, double nowgain, int expectResult, int tolerate) {
Assert.isTrue(data.length == 1200, "data.length must be 1200");
maxval = data[0];
for (int i = 1; i < data.length; i++) {
if (maxval < data[i]) {
@ -47,12 +49,20 @@ public class OptScanModuleLowerCtrlService {
}
}
if (maxval > expectResult + tolerate) {
log.info("maxval: {}", maxval);
if (maxval > 3900) {
scanAgain = true;
suggestGain = nowgain / 2;
return;
}
if (maxval > expectResult + tolerate) {
scanAgain = true;
suggestGain = nowgain * ((double) expectResult / (double) maxval);
return;
}
if (maxval > expectResult) {
scanAgain = false;
suggestGain = nowgain;
@ -132,6 +142,26 @@ public class OptScanModuleLowerCtrlService {
}
public Integer getMaxVal(Integer[] vals) {
Integer maxVal = vals[0];
for (int i = 1; i < vals.length; i++) {
if (maxVal < vals[i]) {
maxVal = vals[i];
}
}
return maxVal;
}
public Integer[] softZoomIntegerVal(Integer[] vals, Integer maxTargetVal) {
Assert.notNull(vals, "vals must not be null");
Integer[] result = new Integer[vals.length];
double magnification = (double) maxTargetVal / getMaxVal(vals);
for (int i = 0; i < vals.length; i++) {
result[i] = (int) Math.round(vals[i] * magnification);
}
return result;
}
public OptRawScanData doOptScan(A8kOptType optType, Boolean autoAmpl) throws AppException {
log.info("doOptScan ............");
log.info(" optType : {}", optType);
@ -142,10 +172,13 @@ public class OptScanModuleLowerCtrlService {
double scanRealGain = 0;
int scanRawGain = 0;
int lasterGain = optModuleExtParamsMgr.getOptLasterRawGain(optType);
int adjustCount = 2;
int adjustCount = 3;
Integer[] result = null;
for (int i = 0; i < adjustCount; i++) {
for (int i = 0; ; i++) {
if (i >= adjustCount) {
break;
}
if (i == 0) {
scanRealGain = optModuleExtParamsMgr.getScanGain(optType);
@ -200,7 +233,13 @@ public class OptScanModuleLowerCtrlService {
rawData.optType = optType;
rawData.scanGain = scanRawGain;
rawData.lasterGain = lasterGain;
rawData.overflow = getMaxVal(result) >= OptConstant.OPT_F_OVERFLOW_VAL;
rawData.rawData = result;
// rawData.rawData = softZoomIntegerVal(result, optType == A8kOptType.TOPT ? OptConstant.OPT_T_TARGET_VAL : OptConstant.OPT_F_FINAL_TARGET_VAL);
if (rawData.overflow) {
log.error("光学扫描结果溢出,建议调整光学增益");
}
return rawData;
}
@ -213,7 +252,7 @@ public class OptScanModuleLowerCtrlService {
result.scanDate = new Date();
OptRawScanData rawData = doOptScan(projBuildinInfo.optcfg.get(subProjIndex).optType, autoAmpl);
A8kOptPeakInfo analysResult = a8KOptCurveAnalyzer.analysCurve(projBuildinInfo.projId, projBuildinInfo.optcfg.get(subProjIndex), rawData.rawData);
A8kOptPeakInfo analysResult = a8KOptCurveAnalyzer.analysisCurve(projBuildinInfo.projId, projBuildinInfo.optcfg.get(subProjIndex), rawData.rawData);
result.rawData = rawData;
result.analysResult = analysResult;

2
src/main/java/a8k/app/service/module/OptScanCtrlModule.java

@ -191,8 +191,6 @@ public class OptScanCtrlModule {
List<ReactionReport.ReactionResult> reactionResults = FakeReactionResultFactory.build(projInfo);
List<OptScanResult> optScanResults = new ArrayList<>();
reactionRecordMgrService.addRecord(sampleInfo, projInfo, optScanResults, reactionResults);
}

18
src/main/java/a8k/app/service/module/SamplePreProcessModule.java

@ -151,7 +151,7 @@ public class SamplePreProcessModule {
synchronized (this) {
try {
docmd("丢TIP", tipOperationCtrlModule::dropTip);
docmd("HBOT复位", hbotMoveCtrlService::moveToZero);
docmd("HBOT复位", hbotMoveExCtrlService::moveQuickToZero);
} catch (AppException e) {
log.error("Error during cleanup", e);
state.setFatalAppError(AppErrorFactory.exceptionToAppError(e));
@ -180,7 +180,7 @@ public class SamplePreProcessModule {
projBuildInInfo.reactionPlateIncubationTimeMin * 60
);
docmd("丢TIP", tipOperationCtrlModule::dropTip);
docmd("HBOT复位", hbotMoveCtrlService::moveToZero);
docmd("HBOT复位", hbotMoveExCtrlService::moveQuickToZero);
}
@ -217,11 +217,15 @@ public class SamplePreProcessModule {
startTask();
actionTaskPool.waitAllDone();
docmd("摇匀模组复位", tubePreProcessCtrlService::resteModule);
docmd("HBOT复位", () -> {
tipOperationCtrlModule.dropTip();
hbotMoveCtrlService.moveToZero();
});
if (actionTaskPool.isHasError()) {
docmd("摇匀模组复位", tubePreProcessCtrlService::resteModule);
docmd("HBOT复位", () -> {
tipOperationCtrlModule.dropTip();
hbotMoveCtrlService.moveToZero();
});
} else {
docmd("HBOT复位", hbotMoveExCtrlService::moveQuickToZero);
}
AppError fatalError = actionTaskPool.getFatalError();

11
src/main/java/a8k/app/type/a8k/container/A8kOptPeakContainer.java

@ -15,6 +15,17 @@ public class A8kOptPeakContainer {
public A8kOptPeak P160 = new A8kOptPeak();
public A8kOptPeak P200 = new A8kOptPeak();
public A8kOptPeak getPeak(int off) {
return switch (off) {
case 0 -> P040;
case 1 -> P080;
case 2 -> P120;
case 3 -> P160;
case 4 -> P200;
default -> throw new IllegalArgumentException("Invalid offset: " + off);
};
}
public void trySetPeakArea(PeakName peakName, Double area) {
Assert.notNull(peakName, "peakName must not be null");
if (peakName.equals(P040.peakName)) {

4
src/main/java/a8k/app/type/a8k/opt/A8kOptPeak.java

@ -32,6 +32,10 @@ public class A8kOptPeak {
return val;
}
public Boolean isFindPeak() {
return state == State.FIND_PEAK;
}
public A8kOptPeak() {
}

1
src/main/java/a8k/app/type/a8k/opt/PeakName.java

@ -6,4 +6,5 @@ public enum PeakName {
H,
T,
C,
N,//
}

4
src/main/java/a8k/app/type/a8k/optfn/A8kOptX.java

@ -1,7 +1,7 @@
package a8k.app.type.a8k.optfn;
public enum A8kOptX {
USUPPORT,
UNSUPPORTED,
R, //1
AR, //2
ATR, //3
@ -25,7 +25,7 @@ public enum A8kOptX {
case 6 -> T4R;
case 7 -> T4T3R;
case 8 -> RFR_ADD_T4T3R;
default -> USUPPORT;
default -> UNSUPPORTED;
};
}
}

13
src/main/java/a8k/app/type/a8k/state/SampleInfo.java

@ -16,4 +16,17 @@ public class SampleInfo implements Serializable {
return String.format("sampleId:%s, isEmergency:%s, bloodType:%s, sampleBarcode:%s, userid:%s",
sampleId, isEmergency, bloodType, sampleBarcode, userid);
}
public SampleInfo() {
//默认构造函数
}
public SampleInfo(String sampleId, Boolean isHighTube, Boolean isEmergency, BloodType bloodType, String sampleBarcode, String userid) {
this.sampleId = sampleId;
this.isHighTube = isHighTube;
this.isEmergency = isEmergency;
this.bloodType = bloodType;
this.sampleBarcode = sampleBarcode;
this.userid = userid;
}
}

6
src/main/java/a8k/extui/mgr/ExtApiPageGroupCfgMgr.java

@ -13,6 +13,7 @@ import a8k.extui.page.extsetting.db.*;
import a8k.extui.page.init.DeviceInitPage;
import a8k.extui.page.measurement.*;
import a8k.extui.page.optalgotest.OptAlgoTestPage;
import a8k.extui.page.test.codetest.OptFormulaTestPage;
import a8k.extui.page.test.codetest.OptFormulaTestPageV2;
import a8k.extui.page.extapp.debug_assistant.FakeReactionRecordGeneratorPage;
@ -89,7 +90,7 @@ public class ExtApiPageGroupCfgMgr {
new Menu(P12TueStateDebugPage.class, "试管"),
new Menu(P13OptModuleStateDebugPage.class, "光学模块"),
new Menu(PreReactionPosStateMgrDebugPage.class, "预反应区状态")
)),
)),
new Menu("数据库", ZList.of(
new Menu(ProjInfoMgrPage.class, "项目信息管理"),
new Menu(ReactionRecordMgrDebugPage.class, "反应记录")
@ -150,7 +151,8 @@ public class ExtApiPageGroupCfgMgr {
new Menu("光学标定与验证", ZList.of(
new Menu(A8kOptVerification.class, "光学模组验证"),
new Menu(OptModuleParamCalibration.class, "光学模块参数校准")
new Menu(OptModuleParamCalibration.class, "光学模块参数校准"),
new Menu(OptAlgoTestPage.class, "光学算法测试")
)),
new Menu("过检专用", ZList.of(
new Menu(P01PipetteGunVerification.class, "移液枪验证"),

70
src/main/java/a8k/extui/page/optalgotest/OptAlgoTestPage.java

@ -0,0 +1,70 @@
package a8k.extui.page.optalgotest;
import a8k.app.dao.type.db.ProjExtInfoCard;
import a8k.app.dao.type.db.ReactionReport;
import a8k.app.hardware.type.A8kEcode;
import a8k.app.optalgo.A8kOptCurveAnalyzer;
import a8k.app.optalgo.A8kPeakAnalyzer;
import a8k.app.optalgo.type.A8kOptPeakInfo;
import a8k.app.service.data.ProjInfoMgrService;
import a8k.app.service.data.ReactionRecordMgrService;
import a8k.app.type.a8k.proj.ProjInfo;
import a8k.app.type.exception.AppException;
import a8k.extui.mgr.ExtApiPageMgr;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Slf4j
public class OptAlgoTestPage {
private final ExtApiPageMgr extApiPageMgr;
private final ReactionRecordMgrService reactionRecordMgrService;
private final ProjInfoMgrService projInfoMgrService;
public ReactionReport.ReactionResult testAlgo(Integer recordId, Integer subProjIndex) throws AppException {
ReactionReport record = reactionRecordMgrService.getRecordById(recordId);
if (record == null) {
log.error("Record with ID {} not found", recordId);
throw AppException.of(A8kEcode.CODEERROR, "RECORD NOT FOUND");
}
var buildInRecordInfo = projInfoMgrService.getProjBuildInInfo(record.projId);
A8kOptCurveAnalyzer a8KOptCurveAnalyzer = new A8kOptCurveAnalyzer();
A8kOptPeakInfo analysResult = a8KOptCurveAnalyzer.analysisCurve(
record.projId,
buildInRecordInfo.optcfg.get(subProjIndex),
record.detailOptData.get(subProjIndex).rawData.rawData
);
// ProjExtInfoCard projExtInfoCard = record.projExtInfoCard;
ProjExtInfoCard projExtInfoCard = null;
if (projExtInfoCard == null) {
projExtInfoCard = projInfoMgrService.getProjExtInfoCard(record.lotId);
}
if (projExtInfoCard.lotId == null || projExtInfoCard.lotId.isEmpty()) {
log.error("Project Ext Info Card lotId is empty for record ID {}", recordId);
throw AppException.of(A8kEcode.CODEERROR, "PROJECT EXT INFO CARD LOT ID IS EMPTY");
}
ProjInfo projInfo = new ProjInfo(projInfoMgrService.getProjBuildInInfo(record.projId), projExtInfoCard);
return A8kPeakAnalyzer.analysisPeakInfo(record.getSampleInfo(), projInfo, subProjIndex, analysResult);
}
@PostConstruct
public void init() {
var page = extApiPageMgr.newPage(this);
page.addFunction("testAlgo", this::testAlgo)
.setParamVal("recordId", () -> 0)
.setParamVal("subProjIndex", () -> 0);
extApiPageMgr.addPage(page);
}
}

3
src/main/java/a8k/extui/page/test/codetest/OptFormulaTestPageV2.java

@ -82,7 +82,6 @@ public class OptFormulaTestPageV2 {
}
A8kOptPeakInfo optAlgoAnalysResult;
@ -128,7 +127,7 @@ public class OptFormulaTestPageV2 {
ProjInfo projInfoContext = getProjInfo();
A8kOptPeakContainer peaks = new A8kOptPeakContainer();
A8kOptCurveAnalyzer.assignPeakName(projInfoContext.buildIn.projId, projInfoContext.buildIn.optcfg.get(optIndex).peakNameRefNum, peaks);
A8kOptCurveAnalyzer.assignPeakName(projInfoContext.buildIn.optcfg.get(optIndex), peaks);
peaks.trySetPeakArea(PeakName.T4, T4Area);
peaks.trySetPeakArea(PeakName.R, RArea);

76
src/main/resources/db/zapp_a8k_project_opt_config.csv

@ -1,38 +1,38 @@
id,projId,projName,subProjIndex,subProjName,subProjShortName,optType,optScanRange,optScanPeakNum,peakNameRefNum
0,1,hsCRP,0,hsCRP,CA,FOPT,150,3,3
0,2,PCT,0,PCT,PC,TOPT,150,2,2
0,3,TSH,0,TSH,TS,FOPT,150,2,2
0,4,PRL,0,PRL,PL,FOPT,150,2,2
0,5,T3,0,T3,T3,FOPT,150,2,2
0,6,T4,0,T4,T4,FOPT,150,2,2
0,7,Total β hCG,0,Total β hCG,HC,FOPT,150,3,3
0,8,LH,0,LH,LH,FOPT,150,2,2
0,9,FSH,0,FSH,FS,FOPT,150,2,2
0,10,Progesterone,0,Progesterone,PG,FOPT,150,2,2
0,12,Tn-I plus,0,Tn-I plus,TG,TOPT,150,3,3
0,13,NT-proBNP,0,NT-proBNP,NB,TOPT,150,3,3
0,14,CK-MB,0,CK-MB ,CK,FOPT,150,2,2
0,15,Myoglobin,0,Myoglobin,MY,FOPT,150,2,2
0,16,D-Dimer,0,D-Dimer,DD,FOPT,150,2,2
0,17,HbAlC,0,HbAlC,HB,FOPT,150,2,2
0,18,PCT plus,0,PCT plus,PP,FOPT,150,3,3
0,20,Tn-I/CK-MB/Myoglobin,0,CK-MB,CK-MB,FOPT,200,3,4
0,20,Tn-I/CK-MB/Myoglobin,1,Myoglobin,Myoglobin,FOPT,200,3,4
0,20,Tn-I/CK-MB/Myoglobin,2,Tn-I,Tn-I,TOPT,200,4,4
0,22,PCT/hsCRP,0,PCT,PCT,TOPT,200,3,4
0,22,PCT/hsCRP,1,hsCRP,hsCRP,FOPT,200,3,4
0,24,SAA,0,SAA,SA,FOPT,150,3,3
0,25,AMH,0,AMH,AM,TOPT,150,3,3
0,26,SAA/CRP,0,SAA,SAA,FOPT,250,5,5
0,26,SAA/CRP,1,CRP,CRP,FOPT,250,5,5
0,27,Vitamin D,0,Vitamin D,VD,FOPT,150,2,2
0,33,ST2,0,ST2,ST,FOPT,150,2,2
0,36,MxA,0,MxA,MX,TOPT,150,2,2
0,48,IL-6,0,IL-6,IL,TOPT,150,3,3
0,49,Gastrin 17,0,Gastrin 17,GA,TOPT,150,2,2
0,50,Pepsinogen I/II,0,Pepsinogen I,PG-I,TOPT,150,3,3
0,50,Pepsinogen I/II,1,Pepsinogen II,PG-II,TOPT,150,3,3
0,52,NT-proBNP/ST2,0,ST2,ST2,FOPT,200,2,4
0,52,NT-proBNP/ST2,1,NT-proBNP,NT-proBNP,TOPT,200,3,4
0,54,Troponin T,0,Troponin T,TT,TOPT,150,3,3
0,55,BNP,0,BNP,BP,TOPT,150,3,3
id,projId,projName,subProjIndex,subProjName,subProjShortName,optType,optScanRange,optScanPeakNum,peakNameRefNum,peakConfig
0,1,hsCRP,0,hsCRP,CA,FOPT,150,3,3,"[H,T,C,N,N]"
0,2,PCT,0,PCT,PC,TOPT,150,2,2,"[N,C,T,N,N]"
0,3,TSH,0,TSH,TS,FOPT,150,2,2,"[N,T,C,N,N]"
0,4,PRL,0,PRL,PL,FOPT,150,2,2,"[N,T,C,N,N]"
0,5,T3,0,T3,T3,FOPT,150,2,2,"[N,T,C,N,N]"
0,6,T4,0,T4,T4,FOPT,150,2,2,"[N,T,C,N,N]"
0,7,Total β hCG,0,Total β hCG,HC,FOPT,150,3,3,"[H,T,C,N,N]"
0,8,LH,0,LH,LH,FOPT,150,2,2,"[N,T,C,N,N]"
0,9,FSH,0,FSH,FS,FOPT,150,2,2,"[N,T,C,N,N]"
0,10,Progesterone,0,Progesterone,PG,FOPT,150,2,2,"[N,T,C,N,N]"
0,12,Tn-I plus,0,Tn-I plus,TG,TOPT,150,3,3,"[H,T,C,N,N]"
0,13,NT-proBNP,0,NT-proBNP,NB,TOPT,150,3,3,"[H,T,C,N,N]"
0,14,CK-MB,0,CK-MB ,CK,FOPT,150,2,2,"[N,T,C,N,N]"
0,15,Myoglobin,0,Myoglobin,MY,FOPT,150,2,2,"[N,T,C,N,N]"
0,16,D-Dimer,0,D-Dimer,DD,FOPT,150,2,2,"[N,T,C,N,N]"
0,17,HbAlC,0,HbAlC,HB,FOPT,150,2,2,"[N,T,C,N,N]"
0,18,PCT plus,0,PCT plus,PP,FOPT,150,3,3,"[H,T,C,N,N]"
0,20,Tn-I/CK-MB/Myoglobin,0,CK-MB,CK-MB,FOPT,200,3,4,"[N,H,T,C,N]"
0,20,Tn-I/CK-MB/Myoglobin,1,Myoglobin,Myoglobin,FOPT,200,3,4,"[N,H,T,C,N]"
0,20,Tn-I/CK-MB/Myoglobin,2,Tn-I,Tn-I,TOPT,200,4,4,"[R,H,T,C,N]"
0,22,PCT/hsCRP,0,PCT,PCT,TOPT,200,3,4,"[N,H,T,C,N]"
0,22,PCT/hsCRP,1,hsCRP,hsCRP,FOPT,200,3,4,"[R,N,T,C,N]"
0,24,SAA,0,SAA,SA,FOPT,150,3,3,"[H,T,C,N,N]"
0,25,AMH,0,AMH,AM,TOPT,150,3,3,"[H,T,C,N,N]"
0,26,SAA/CRP,0,SAA,SAA,FOPT,250,5,5,"[T4,R,H,T,C]"
0,26,SAA/CRP,1,CRP,CRP,FOPT,250,5,5,"[T4,R,H,T,C]"
0,27,Vitamin D,0,Vitamin D,VD,FOPT,150,2,2,"[N,T,C,N,N]"
0,33,ST2,0,ST2,ST,FOPT,150,2,2,"[N,T,C,N,N]"
0,36,MxA,0,MxA,MX,TOPT,150,2,2,"[N,T,C,N,N]"
0,48,IL-6,0,IL-6,IL,TOPT,150,3,3,"[H,T,C,N,N]"
0,49,Gastrin 17,0,Gastrin 17,GA,TOPT,150,2,2,"[N,T,C,N,N]"
0,50,Pepsinogen I/II,0,Pepsinogen I,PG-I,TOPT,150,3,3,"[H,T,C,N,N]"
0,50,Pepsinogen I/II,1,Pepsinogen II,PG-II,TOPT,150,3,3,"[H,T,C,N,N]"
0,52,NT-proBNP/ST2,0,ST2,ST2,FOPT,200,2,4,"[N,N,T,C,N]"
0,52,NT-proBNP/ST2,1,NT-proBNP,NT-proBNP,TOPT,200,3,4,"[R,H,N,C,N]"
0,54,Troponin T,0,Troponin T,TT,TOPT,150,3,3,"[H,T,C,N,N]"
0,55,BNP,0,BNP,BP,TOPT,150,3,3,"[H,T,C,N,N]"

BIN
tools/20250427-F-光学光学报告.xlsx

BIN
tools/20250427-T-光学光学报告-2.xlsx

Loading…
Cancel
Save