13 changed files with 708 additions and 20 deletions
-
6pom.xml
-
228src/main/java/com/dreamworks/boditech/MyApplication.java
-
1src/main/java/com/dreamworks/boditech/driver/DeviceCommand.java
-
32src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorBase.java
-
203src/main/java/com/dreamworks/boditech/driver/task/step/StepAnalysis.java
-
39src/main/java/com/dreamworks/boditech/model/MdbIdChip.java
-
81src/main/java/com/dreamworks/boditech/model/MyActiveRecord.java
-
46src/main/java/com/dreamworks/boditech/model/MyActiveRecordCriteria.java
-
10src/main/java/com/dreamworks/boditech/model/MyActiveRecordField.java
-
47src/main/java/com/dreamworks/boditech/model/MyActiveRecordMapper.java
-
11src/main/java/com/dreamworks/boditech/utils/MyCommon.java
-
BINsrc/main/java/com/dreamworks/boditech/utils/MyOptAlgo$AlgoResult.class
-
24src/main/java/com/dreamworks/boditech/utils/MyOptAlgo.java
@ -0,0 +1,228 @@ |
|||
package com.dreamworks.boditech; |
|||
import com.dreamworks.boditech.driver.Device; |
|||
import com.dreamworks.boditech.driver.actuator.ActAnalysisScanner; |
|||
import com.dreamworks.boditech.driver.actuator.ActMotor; |
|||
import com.dreamworks.boditech.driver.actuator.ActuatorModule; |
|||
import com.dreamworks.boditech.model.MdbIdChip; |
|||
import com.dreamworks.boditech.model.MyActiveRecord; |
|||
import com.dreamworks.boditech.utils.MyCommon; |
|||
import com.dreamworks.boditech.utils.MyOptAlgo; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import jakarta.annotation.Resource; |
|||
import org.jetbrains.annotations.NotNull; |
|||
import org.springframework.beans.BeansException; |
|||
import org.springframework.boot.ApplicationArguments; |
|||
import org.springframework.boot.ApplicationRunner; |
|||
import org.springframework.context.ApplicationContext; |
|||
import org.springframework.context.ApplicationContextAware; |
|||
import org.springframework.core.annotation.Order; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.nio.ByteOrder; |
|||
import java.util.*; |
|||
|
|||
@Order(1) |
|||
@Component |
|||
public class MyApplication implements ApplicationRunner, ApplicationContextAware { |
|||
// application context |
|||
private static ApplicationContext context; |
|||
|
|||
// get application context |
|||
public static ApplicationContext getContext() { |
|||
return MyApplication.context; |
|||
} |
|||
|
|||
// get bean |
|||
public static <T> T getBean(Class<T> beanClass) { |
|||
return context.getBean(beanClass); |
|||
} |
|||
|
|||
@Override |
|||
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { |
|||
MyApplication.context = applicationContext; |
|||
} |
|||
|
|||
|
|||
@Resource |
|||
public Device device; |
|||
|
|||
@Override |
|||
public void run(ApplicationArguments args) throws Exception { |
|||
var analysisScanMotor = (ActMotor)device.getActuator(ActuatorModule.ANALYSIS_SCAN_MOTOR); |
|||
|
|||
// analysisScanMotor.reset(); |
|||
|
|||
this.analysis(); |
|||
} |
|||
|
|||
private void analysis() { |
|||
var scanner = (ActAnalysisScanner)device.getActuator(ActuatorModule.ANALYSIS_SCAN_SCANNER); |
|||
|
|||
var sampleType = "wb"; |
|||
var idChip = MyActiveRecord.findById(MdbIdChip.class, 2); |
|||
assert idChip != null; |
|||
|
|||
// 配置相机 |
|||
if (Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_F)) { // F 光学扫描 |
|||
scanner.setRegister(4200, 0); // 设置扫描类型 |
|||
scanner.setRegister(4201, 2860); // 设置扫描开始坐标 |
|||
scanner.setRegister(4202, -1); // 设置扫描方向 |
|||
scanner.setRegister(4203, 1); // 设置扫描间隔 |
|||
scanner.setRegister(4204, 1200); // 设置扫描点数 |
|||
scanner.setRegister(4205, 1); // 选通通道 |
|||
scanner.setRegister(4206, 0); // 激光增益 |
|||
scanner.setRegister(4207, 0); // 扫描器增益 |
|||
scanner.setRegister(40, 7); // 刷新激光器增益到mcp41xxx |
|||
scanner.setRegister(40, 8); // 刷新设置扫描器增益到mcp41xxx |
|||
scanner.setRegister(40, 1); // 选通通道 |
|||
} else if (Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_T)) { // T 光学扫描 |
|||
scanner.setRegister(4200, 1); // 设置扫描类型 |
|||
scanner.setRegister(4201, 4005); // 设置扫描开始坐标 |
|||
scanner.setRegister(4202, -1); // 设置扫描方向 |
|||
scanner.setRegister(4203, 1); // 设置扫描间隔 |
|||
scanner.setRegister(4204, 1200); // 设置扫描点数 |
|||
scanner.setRegister(4205, 1); // 选通通道 |
|||
scanner.setRegister(4206, 0); // 激光增益 |
|||
scanner.setRegister(4207, 0); // 扫描器增益 |
|||
scanner.setRegister(40, 7); // 刷新激光器增益到mcp41xxx |
|||
scanner.setRegister(40, 8); // 刷新设置扫描器增益到mcp41xxx |
|||
scanner.setRegister(40, 1); // 选通通道 |
|||
} else { |
|||
throw new RuntimeException("unknown scan type" + idChip.scanType); |
|||
} |
|||
|
|||
// 开始扫描 |
|||
scanner.start(); |
|||
MyCommon.easySleep(20000); // 延时10s @TODO : 这里要根据不同的扫描类型进行延时 |
|||
|
|||
// 读取扫描数据 |
|||
var buffer = scanner.readRaw(); |
|||
buffer.order(ByteOrder.LITTLE_ENDIAN); |
|||
float[] data = new float[buffer.capacity() / 2]; |
|||
for (int i = 0; i < data.length; i++) { |
|||
data[i] = buffer.getShort(); |
|||
} |
|||
|
|||
// 分析数据 |
|||
var algo = new MyOptAlgo(); |
|||
var result = algo.calculate(data, idChip.scanPeakCount); |
|||
|
|||
// 结算结果 |
|||
List<Double> values = new ArrayList<>(); |
|||
var projects = MyCommon.jsonToNode(idChip.items); |
|||
for ( var i=0; i< idChip.itemCount; i++ ) { |
|||
double valueX = 0; |
|||
JsonNode funcInfo = null; |
|||
|
|||
var project = projects.get(i); |
|||
var itemName = project.get("item").asText(); |
|||
var isPiecewiseFunction = project.get("isPiecewiseFunction").asBoolean(); |
|||
if ( !isPiecewiseFunction ) { // 非分段函数 |
|||
var func = project.get("nonPiecewiseFunction"); |
|||
var xValueSource = func.get("xValueSource").asInt(); |
|||
valueX = this.calculateRatio(result, xValueSource, idChip, itemName); |
|||
funcInfo = func.get("wb"); |
|||
if ("serum".equals(sampleType)) { |
|||
funcInfo = project.get("serum"); |
|||
} |
|||
} else { // 分段函数 |
|||
var func = project.get("piecewiseFunction"); |
|||
var piecewiseValueSrc = func.get("piecewiseValueSrc").asInt(); |
|||
var piecewiseValueSrcValue = this.calculateRatio(result, piecewiseValueSrc, idChip, itemName); |
|||
var piecewiseValue = func.get("piecewiseValue").asDouble(); |
|||
if ( piecewiseValueSrcValue > piecewiseValue ) { // 高浓度 |
|||
var highXValueSource = func.get("highXValueSource").asInt(); |
|||
valueX = this.calculateRatio(result, highXValueSource, idChip, itemName); |
|||
funcInfo = func.get("wbHigh"); |
|||
if ("serum".equals(sampleType)) { |
|||
funcInfo = project.get("serumHigh"); |
|||
} |
|||
} else { // 低浓度 |
|||
var lowXValueSource = func.get("lowXValueSource").asInt(); |
|||
valueX = this.calculateRatio(result, lowXValueSource, idChip, itemName); |
|||
funcInfo = func.get("wbLow"); |
|||
if ("serum".equals(sampleType)) { |
|||
funcInfo = project.get("serumLow"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
double valueD = funcInfo.get("d").asDouble(); |
|||
double valueB = funcInfo.get("b").asDouble(); |
|||
double valueC = funcInfo.get("c").asDouble(); |
|||
double valueA = funcInfo.get("a").asDouble(); |
|||
double value = valueA * Math.pow(valueX,3) + valueB * Math.pow(valueX,2) + valueC * valueX + valueD; |
|||
values.add(value); |
|||
System.out.println("扫描完成 : " + value); |
|||
} |
|||
|
|||
System.out.println("扫描完成 : " + values.size()); |
|||
} |
|||
|
|||
/** |
|||
* @link https://iflytop1.feishu.cn/docx/UnRtdSG4qouMTaxzRb2cjiTTnZe |
|||
* @link https://iflytop1.feishu.cn/docx/A0CHdQL6OoTTCSx8MB7cQKEjnSc |
|||
* @param result |
|||
* @param xSource |
|||
* @return |
|||
*/ |
|||
private double calculateRatio(MyOptAlgo.AlgoResult result, int xSource, MdbIdChip idChip, String itenName) { |
|||
// 计算 ratio |
|||
Map<String, Float> ratios = new HashMap<>(); |
|||
if ( 5 == idChip.scanPeakCount ) { |
|||
var peaks = result.peakInfos; |
|||
ratios.put("ratio", peaks[3].area / peaks[4].area); // T/C |
|||
ratios.put("antiRatio", peaks[3].area / peaks[4].area); // H/C |
|||
ratios.put("antiTestRatio", peaks[3].area / peaks[2].area); // T/H |
|||
ratios.put("rfRatio", peaks[1].area / peaks[4].area); // R/C |
|||
ratios.put("rtRatio", peaks[3].area / peaks[1].area); // T/R |
|||
ratios.put("t4Ratio", peaks[0].area / peaks[4].area); // T4/C |
|||
ratios.put("t4t3Ratio", peaks[1].area / peaks[0].area); // R/T4 |
|||
} else if ( 4 == idChip.scanPeakCount && Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_F) ) { |
|||
var peaks = result.peakInfos; // H T C |
|||
ratios.put("ratio", peaks[1].area / peaks[2].area); // T/C |
|||
ratios.put("antiRatio", peaks[0].area / peaks[2].area); // H/C |
|||
ratios.put("antiTestRatio", peaks[1].area / peaks[0].area); // T/H |
|||
ratios.put("rfRatio", peaks[0].area / peaks[2].area); // R/C @TODO : 待定, 三联卡F光学没有R |
|||
ratios.put("rtRatio", peaks[1].area / peaks[0].area); // T/R @TODO : 待定, 三联卡F光学没有R |
|||
} else if ( 4 == idChip.scanPeakCount && Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_T) ) { |
|||
var peaks = result.peakInfos; // R H T C |
|||
ratios.put("ratio", peaks[2].area / peaks[3].area); // T/C |
|||
ratios.put("antiRatio", peaks[1].area / peaks[3].area); // H/C |
|||
ratios.put("antiTestRatio", peaks[2].area / peaks[1].area); // T/H |
|||
ratios.put("rfRatio", peaks[0].area / peaks[3].area); // R/C |
|||
ratios.put("rtRatio", peaks[2].area / peaks[0].area); // T/R |
|||
} else if ( 3 == idChip.scanPeakCount ) { |
|||
var peaks = result.peakInfos; // H T C |
|||
ratios.put("ratio", peaks[1].area / peaks[2].area); // T/C |
|||
ratios.put("antiRatio", peaks[0].area / peaks[2].area); // H/C |
|||
ratios.put("antiTestRatio", peaks[1].area / peaks[0].area); // T/H |
|||
} else if ("PCT".equals(itenName)) { |
|||
var peaks = result.peakInfos; // C T |
|||
ratios.put("ratio", peaks[1].area / peaks[0].area); // T/C |
|||
} else if ( 2 == idChip.scanPeakCount ) { |
|||
var peaks = result.peakInfos; // T C |
|||
ratios.put("ratio", peaks[0].area / peaks[1].area); // T/C |
|||
} else { |
|||
throw new RuntimeException("unknown scan peak count" + idChip.scanPeakCount); |
|||
} |
|||
|
|||
// calculate ratio by x source |
|||
if ( 1 == xSource ) { |
|||
return ratios.get("ratio"); |
|||
} else if ( 2 == xSource ) { |
|||
return ratios.get("antiTestRatio"); |
|||
} else if ( 3 == xSource ) { |
|||
return ratios.get("antiRatio"); |
|||
} else if ( 4 == xSource ) { |
|||
return ratios.get("ratio") + ratios.get("antiTestRatio"); |
|||
} else if ( 5 == xSource ) { |
|||
// @TODO : 这里 R-ratio 不知道是啥 |
|||
throw new RuntimeException("不知道 R-Ratio 是啥"); |
|||
} else if ( 6 == xSource ) { |
|||
return ratios.get("t4Ratio"); |
|||
} else { |
|||
throw new RuntimeException("unknown x source" + xSource); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
package com.dreamworks.boditech.model; |
|||
public class MdbIdChip extends MyActiveRecord { |
|||
public static final Integer SCAN_TYPE_AUTO = 0; |
|||
public static final Integer SCAN_TYPE_F = 1; |
|||
public static final Integer SCAN_TYPE_T = 2; |
|||
public static final Integer SCAN_TYPE_F_T = 3; |
|||
|
|||
@MyActiveRecordField |
|||
public Long id; |
|||
|
|||
@MyActiveRecordField |
|||
public String item; |
|||
|
|||
@MyActiveRecordField |
|||
public Integer itemCount; |
|||
|
|||
@MyActiveRecordField |
|||
public String lotCode; |
|||
|
|||
@MyActiveRecordField |
|||
public Integer expiredDate; |
|||
|
|||
@MyActiveRecordField |
|||
public Integer version; |
|||
|
|||
@MyActiveRecordField |
|||
public Integer scanType; |
|||
|
|||
@MyActiveRecordField |
|||
public Integer scanPeakCount; |
|||
|
|||
@MyActiveRecordField |
|||
public String items; |
|||
|
|||
// get table name |
|||
public static String getTableName() { |
|||
return "bdt_id_chips"; |
|||
} |
|||
} |
@ -0,0 +1,81 @@ |
|||
package com.dreamworks.boditech.model; |
|||
import com.dreamworks.boditech.MyApplication; |
|||
|
|||
import java.lang.reflect.Constructor; |
|||
import java.lang.reflect.Field; |
|||
import java.lang.reflect.InvocationTargetException; |
|||
import java.lang.reflect.Method; |
|||
import java.util.Map; |
|||
public class MyActiveRecord { |
|||
// find model by given id |
|||
public static <T extends MyActiveRecord> T findById(Class<T> clazz , int id ) { |
|||
var mapper = MyApplication.getBean(MyActiveRecordMapper.class); |
|||
var criteria = new MyActiveRecordCriteria(); |
|||
criteria.tableName = MyActiveRecord.getTableNameFromModelClass(clazz); |
|||
criteria.limit = 1; |
|||
criteria.whereIs("id", id); |
|||
var rows = mapper.find(criteria); |
|||
if (rows.isEmpty()) { |
|||
return null; |
|||
} |
|||
return MyActiveRecord.fillModel(clazz, rows.get(0)); |
|||
} |
|||
|
|||
// get table name from model class |
|||
private static <T> String getTableNameFromModelClass(Class<T> modelClass) { |
|||
Method tableNameGetter = null; |
|||
try { |
|||
tableNameGetter = modelClass.getMethod("getTableName"); |
|||
} catch (NoSuchMethodException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
String tableName = null; |
|||
try { |
|||
tableName = (String)tableNameGetter.invoke(null); |
|||
} catch (IllegalAccessException | InvocationTargetException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
return tableName; |
|||
} |
|||
|
|||
// fill model |
|||
private static <T> T fillModel(Class<T> modelClass, Map<String,Object> data) { |
|||
Constructor<?> modelConstructor = null; |
|||
try { |
|||
modelConstructor = modelClass.getDeclaredConstructor(); |
|||
} catch (NoSuchMethodException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
|
|||
T model = null; |
|||
try { |
|||
model = modelClass.cast(modelConstructor.newInstance()); |
|||
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
|
|||
for ( Map.Entry<String,Object> entry : data.entrySet() ) { |
|||
String key = entry.getKey(); |
|||
Field field = null; |
|||
try { |
|||
field = model.getClass().getDeclaredField(key); |
|||
} catch (NoSuchFieldException e) { |
|||
continue ; |
|||
} |
|||
|
|||
Class<?> fieldType = field.getType(); |
|||
if ( !fieldType.isAssignableFrom(entry.getValue().getClass()) ) { |
|||
throw new RuntimeException("Attribute type mismatch: " + key); |
|||
} |
|||
|
|||
field.setAccessible(true); |
|||
try { |
|||
field.set(model, entry.getValue()); |
|||
} catch (IllegalAccessException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
return model; |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
package com.dreamworks.boditech.model; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
public class MyActiveRecordCriteria { |
|||
// condition |
|||
public static class Condition { |
|||
public String name; |
|||
public String operator; |
|||
public Object value; |
|||
} |
|||
|
|||
// table name |
|||
public String tableName; |
|||
// conditions |
|||
public List<Condition> conditions; |
|||
// limit |
|||
public Integer limit; |
|||
// offset |
|||
public Integer offset; |
|||
|
|||
// where is |
|||
public void whereIs(String key, Object value) { |
|||
var condition = new Condition(); |
|||
condition.name = key; |
|||
condition.operator = "="; |
|||
condition.value = value; |
|||
this.appendCondition(condition); |
|||
} |
|||
|
|||
// where is not |
|||
public void whereIsNot(String key, Object value) { |
|||
var condition = new Condition(); |
|||
condition.name = key; |
|||
condition.operator = "!="; |
|||
condition.value = value; |
|||
this.appendCondition(condition); |
|||
} |
|||
|
|||
// append condition |
|||
private void appendCondition( Condition condition ) { |
|||
if ( this.conditions == null ) { |
|||
this.conditions = new ArrayList<>(); |
|||
} |
|||
this.conditions.add(condition); |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
package com.dreamworks.boditech.model; |
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target({ElementType.FIELD}) |
|||
public @interface MyActiveRecordField { |
|||
|
|||
} |
@ -0,0 +1,47 @@ |
|||
package com.dreamworks.boditech.model; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
import org.apache.ibatis.annotations.Select; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
@Mapper |
|||
public interface MyActiveRecordMapper { |
|||
@Select( |
|||
"<script>" + |
|||
"SELECT * FROM ${tableName} " + |
|||
"<if test='conditions != null'>" + |
|||
"WHERE " + |
|||
"<foreach collection='conditions' item='value' separator=' AND '>" + |
|||
"<if test='value.operator == \"=\"'>${value.name} = #{value.value}</if>" + |
|||
"<if test='value.operator == \"!=\"'>${value.name} != #{value.value}</if>" + |
|||
"<if test='value.operator == \"<\"'>${value.name} < #{value.value}</if>" + |
|||
"<if test='value.operator == \"<=\"'>${value.name} <= #{value.value}</if>" + |
|||
"<if test='value.operator == \">\"'>${value.name} > #{value.value}</if>" + |
|||
"<if test='value.operator == \">=\"'>${value.name} >= #{value.value}</if>" + |
|||
"<if test='value.operator == \"LIKE\"'>${value.name} LIKE #{value.value}</if>" + |
|||
"<if test='value.operator == \"IN\"'>${value.name} IN " + |
|||
"<foreach collection='value.value' item='v' open='(' separator=',' close=')'>#{v}</foreach>" + |
|||
"</if>" + |
|||
"<if test='value.operator == \"NOT IN\"'>${value.name} NOT IN " + |
|||
"<foreach collection='value.value' item='v' open='(' separator=',' close=')'>#{v}</foreach>" + |
|||
"</if>" + |
|||
"<if test='value.operator == \"IS NULL\"'>${value.name} IS NULL</if>" + |
|||
"<if test='value.operator == \"IS NOT NULL\"'>${value.name} IS NOT NULL</if>" + |
|||
"<if test='value.operator == \"BETWEEN\"'>${value.name} BETWEEN #{value.value[0]} AND #{value.value[1]}</if>" + |
|||
"<if test='value.operator == \"NOT BETWEEN\"'>${value.name} NOT BETWEEN #{value.value[0]} AND #{value.value[1]}</if>" + |
|||
"</foreach>" + |
|||
"</if> " + |
|||
"ORDER BY id DESC " + |
|||
"<if test='limit != null'>LIMIT #{limit}</if> " + |
|||
"<if test='offset != null'>OFFSET #{offset}</if>" + |
|||
"</script>" |
|||
) |
|||
List<Map<String,Object>> find(MyActiveRecordCriteria criteria); |
|||
|
|||
void insert(); |
|||
void update(); |
|||
void delete(); |
|||
void findOne(); |
|||
void find(); |
|||
void batchUpdate(); |
|||
void batchDelete(); |
|||
} |
@ -0,0 +1,24 @@ |
|||
package com.dreamworks.boditech.utils; |
|||
public class MyOptAlgo { |
|||
public static class AlgoResult { |
|||
public static class PeakInfo { |
|||
public boolean findPeak; |
|||
public float peakFullArea; |
|||
public float peakBaseLineArea; |
|||
public float area; |
|||
public int peakPos; |
|||
public int peakStartPos; |
|||
public int peakEndPos; |
|||
} |
|||
|
|||
public int peakNum; |
|||
public float[] lineAvg250; |
|||
public PeakInfo[] peakInfos; |
|||
} |
|||
|
|||
static { |
|||
System.load("D:/Sige5193/boditech-opt-algo-java-lib/x64/Debug/boditech-opt-algo-java-lib.dll"); |
|||
} |
|||
|
|||
public native AlgoResult calculate(float[] data, int peakCount); |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue