diff --git a/app.db b/app.db index 5abc800..bcc0fa2 100644 Binary files a/app.db and b/app.db differ diff --git a/pom.xml b/pom.xml index 71aa56c..eb7d419 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.iflytop uf - 0.0.21 + 0.0.24 diff --git a/src/main/java/com/iflytop/a800/controller/DemoController.java b/src/main/java/com/iflytop/a800/controller/DemoController.java index 65c9acf..c52bbef 100644 --- a/src/main/java/com/iflytop/a800/controller/DemoController.java +++ b/src/main/java/com/iflytop/a800/controller/DemoController.java @@ -1,6 +1,11 @@ package com.iflytop.a800.controller; +import com.iflytop.a800.TaskManager; import com.iflytop.a800.device.Device; +import com.iflytop.a800.model.MdbProject; import com.iflytop.a800.resource.BufferTube; +import com.iflytop.a800.resource.TestTube; +import com.iflytop.a800.task.TubeTestTask; +import com.iflytop.uf.UfActiveRecord; import com.iflytop.uf.UfActuatorCmdExecutor; import com.iflytop.uf.controller.UfApiControllerBase; import com.iflytop.uf.controller.UfApiResponse; @@ -75,4 +80,17 @@ public class DemoController extends UfApiControllerBase { } return this.success(); } + + @PostMapping("/api/demo/tube-test-task-start") + @ResponseBody + public UfApiResponse tubeTestTaskStart() { + var task = new TubeTestTask(); + task.project = UfActiveRecord.findOne(MdbProject.class, "1"); + task.tube = new TestTube(); + task.tube.type = "EmergencyTube"; + task.tube.index = 0; + var taskMan = TaskManager.getInstance(); + taskMan.append(task); + return this.success(); + } } diff --git a/src/main/java/com/iflytop/a800/device/Device.java b/src/main/java/com/iflytop/a800/device/Device.java index 276a79e..c043fbc 100644 --- a/src/main/java/com/iflytop/a800/device/Device.java +++ b/src/main/java/com/iflytop/a800/device/Device.java @@ -2,6 +2,7 @@ package com.iflytop.a800.device; import com.iflytop.a800.resource.BufferTubeManager; import com.iflytop.a800.resource.LargeBufferTubeManager; +import com.iflytop.a800.resource.TestCardManager; public class Device { // singleton instance @@ -10,10 +11,16 @@ public class Device { public final Feeder feeder = new Feeder(); // pipette public final Pipette pipette = new Pipette(); + // incubator + public final Incubator incubator = new Incubator(); + // scanner + public final Scanner scanner = new Scanner(); // buffer tube manager public final BufferTubeManager bufferTube = new BufferTubeManager(); // large buffer tube manager public final LargeBufferTubeManager largeBufferTube = new LargeBufferTubeManager(); + // test card manager + public final TestCardManager testCard = new TestCardManager(); // get instance public static Device getInstance() { diff --git a/src/main/java/com/iflytop/a800/device/Feeder.java b/src/main/java/com/iflytop/a800/device/Feeder.java index 8e7b4b2..9dbdcae 100644 --- a/src/main/java/com/iflytop/a800/device/Feeder.java +++ b/src/main/java/com/iflytop/a800/device/Feeder.java @@ -12,9 +12,9 @@ public class Feeder { } // 读取试管架类型 - public Number readTubeRackType() { + public String readTubeRackType() { UfCmdSnippetExecutor.execute("FeedTubeRackTypeReadPrepare"); - return 1; + return "wb"; } // 读取试管是否存在 diff --git a/src/main/java/com/iflytop/a800/device/Incubator.java b/src/main/java/com/iflytop/a800/device/Incubator.java index 323c007..9ecfb4f 100644 --- a/src/main/java/com/iflytop/a800/device/Incubator.java +++ b/src/main/java/com/iflytop/a800/device/Incubator.java @@ -1,4 +1,17 @@ package com.iflytop.a800.device; - +import com.iflytop.uf.UfCmdSnippetExecutor; +import com.iflytop.uf.model.UfMdbOption; +import java.util.Map; public class Incubator { + // 推送新卡片 + public void pushNewCard( Integer boxIndex ) { + Integer boxPos = UfMdbOption.getInteger(String.format("TestCardWarehouseBox.%d", boxIndex)); + Map params = Map.of("box", boxPos); + UfCmdSnippetExecutor.execute("IncubatorTestCardPushIn", params); + } + + // 退出到扫描 + public void exitToScanner() { + UfCmdSnippetExecutor.execute("IncubatorTestCardExitToScanner"); + } } diff --git a/src/main/java/com/iflytop/a800/device/Pipette.java b/src/main/java/com/iflytop/a800/device/Pipette.java index 9efc0f0..0c7136d 100644 --- a/src/main/java/com/iflytop/a800/device/Pipette.java +++ b/src/main/java/com/iflytop/a800/device/Pipette.java @@ -60,20 +60,20 @@ public class Pipette { Integer x = zoneStartX + indexX * distanceX; Integer y = zoneStartY + indexY * distanceY; + Boolean tipStatusCheckEnable = UfMdbOption.getBoolean("PipetteTipStatusCheckEnable"); Map pickUpParams = Map.of("tipX", x, "tipY", y, "tipZ", zoneZ); // 尝试三次拾取枪头 for ( int i=0; i<3; i++ ) { LOG.info("[Pipette] Pick up tip at [{},{}](x={}, y={}, z={})", indexX, indexY, x, y, zoneZ); UfCmdSnippetExecutor.execute("PipetteTipPickUp", pickUpParams); String tipState = UfActuatorCmdExecutor.execute("Pipette", "read_pipette_tip_state"); - if ("1".equals(tipState)) { + if ( "1".equals(tipState) || !tipStatusCheckEnable ) { break ; } - } String tipState = UfActuatorCmdExecutor.execute("Pipette", "read_pipette_tip_state"); - if ("0".equals(tipState)) { + if ("0".equals(tipState) && tipStatusCheckEnable ) { this.tipPickUp(); return ; // 如果拾取失败,则跳过这个TIP取下一个 } @@ -139,6 +139,75 @@ public class Pipette { UfCmdSnippetExecutor.execute(snippetKey, dispenseParams); } + // 混匀 + public void mix( BufferTube tube, Integer count, Integer volume ) { + if ( 0 == count ) { + return ; + } + + Integer startX = UfMdbOption.getInteger(String.format("%sZoneStartX.%d", tube.type, tube.zoneIndex)); + Integer startY = UfMdbOption.getInteger(String.format("%sZoneStartY.%d", tube.type, tube.zoneIndex)); + Integer distanceX = UfMdbOption.getInteger(String.format("%sDistanceX", tube.type)); + Integer distanceY = UfMdbOption.getInteger(String.format("%sDistanceY", tube.type)); + Integer indexX = tube.tubeIndex % 5; + Integer indexY = tube.tubeIndex / 5; + Integer tubeX = startX + indexX * distanceX; + Integer tubeY = startY + indexY * distanceY; + Map dispenseParams = Map.of("tubeX", tubeX, "tubeY", tubeY); + + String snippetKey = "SampleMixingAtDetectionTubePrepare"; + if ( "BufferTube".equals(tube.type) ) { + snippetKey = "SampleMixingAtBufferTubePrepare"; + } + UfCmdSnippetExecutor.execute(snippetKey, dispenseParams); + + String volumeStr = Integer.toString(volume); + for ( int i=0; i dispenseParams = Map.of("tubeX", tubeX, "tubeY", tubeY); + + String snippetKey = "PunctureAtDetectionTube"; + if ( "BufferTube".equals(tube.type) ) { + snippetKey = "PunctureAtBufferTube"; + } + UfCmdSnippetExecutor.execute(snippetKey, dispenseParams); + } + + // 从缓冲液管吸液并吐液到检测板 + public void aspirateFromBufferTubeAndDispenseToTestCard( BufferTube tube, Integer volume ) { + Integer startX = UfMdbOption.getInteger(String.format("%sZoneStartX.%d", tube.type, tube.zoneIndex)); + Integer startY = UfMdbOption.getInteger(String.format("%sZoneStartY.%d", tube.type, tube.zoneIndex)); + Integer distanceX = UfMdbOption.getInteger(String.format("%sDistanceX", tube.type)); + Integer distanceY = UfMdbOption.getInteger(String.format("%sDistanceY", tube.type)); + Integer indexX = tube.tubeIndex % 5; + Integer indexY = tube.tubeIndex / 5; + Integer tubeX = startX + indexX * distanceX; + Integer tubeY = startY + indexY * distanceY; + Map dispenseParams = Map.of("tubeX", tubeX, "tubeY", tubeY, "volume", volume); + + String snippetKey = "AspirateFromDetectionTubeAndDispenseToTestCard"; + if ( "BufferTube".equals(tube.type) ) { + snippetKey = "AspirateFromBufferTubeAndDispenseToTestCard"; + } + UfCmdSnippetExecutor.execute(snippetKey, dispenseParams); + } + // 从大缓冲液管吸液 public void aspirateFromLargeBufferTube(LargeBufferTube tube, Integer volume) { Integer startX = UfMdbOption.getInteger("LargeBufferTubeZoneStartX"); @@ -208,8 +277,23 @@ public class Pipette { this.aspirateWithLiquidLevelFollow(volume, depth); } + // 从全血5ml吸液 + public void aspirateFromWholeBlood5ml(Integer index, Integer volume) { + throw new RuntimeException("摇匀没接,暂时无法测试"); + } + + // 从全血3ml吸液 + public void aspirateFromWholeBlood3ml(Integer index, Integer volume) { + throw new RuntimeException("摇匀没接,暂时无法测试"); + } + // 移动到液面 private void moveToLiquidLevel( Integer maxDepth, Integer threshold ) { + Boolean liquidLevelDetectEnable = UfMdbOption.getBoolean("PipetteLiquidLevelDetectEnable"); + if ( !liquidLevelDetectEnable ) { + return ; + } + int stepDepth = 10; int depth = 0; do { diff --git a/src/main/java/com/iflytop/a800/device/Scanner.java b/src/main/java/com/iflytop/a800/device/Scanner.java index 6ea0070..47fa1b5 100644 --- a/src/main/java/com/iflytop/a800/device/Scanner.java +++ b/src/main/java/com/iflytop/a800/device/Scanner.java @@ -1,4 +1,59 @@ package com.iflytop.a800.device; +import com.iflytop.uf.UfActuatorCmdExecutor; +import com.iflytop.uf.UfCmdSnippetExecutor; +import com.iflytop.uf.util.UfCommon; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; public class Scanner { + // 扫描类型F + public void scanTypeF() { + UfCmdSnippetExecutor.execute("SampleResultAnalysisTypeF"); + UfCommon.delay(20 * 1000); + } + + // 扫描类型T + public void scanTypeT() { + UfCmdSnippetExecutor.execute("SampleResultAnalysisTypeT"); + UfCommon.delay(20 * 1000); + } + + // 读取结果 + public float[] readResult() { + String sizeStr = UfActuatorCmdExecutor.execute("Scanner","module_get_reg", "10"); + String countStr = UfActuatorCmdExecutor.execute("Scanner","module_get_reg", "11"); + int size = Integer.parseInt(sizeStr); + int count = Integer.parseInt(countStr); + List data = new ArrayList<>(); + for ( int i = 0; i < count; i++ ) { + String readParam = Integer.toString(i); + String response = UfActuatorCmdExecutor.execute("Scanner","module_read_raw", readParam); + Base64.Decoder decoder = Base64.getDecoder(); + byte[] bytes = decoder.decode(response); + for (byte aByte : bytes) { + data.add(aByte); + } + UfCommon.delay(100); + } + + byte[] result = new byte[data.size()]; + for ( int i = 0; i < data.size(); i++ ) { + result[i] = data.get(i); + } + var buffer = ByteBuffer.wrap(result); + buffer.order(ByteOrder.LITTLE_ENDIAN); + float[] scanRawData = new float[buffer.capacity() / 2]; + for (int i = 0; i < scanRawData.length; i++) { + scanRawData[i] = buffer.getShort(); + } + return scanRawData; + } + + // 丢弃卡片 + public void dropCard() { + UfCmdSnippetExecutor.execute("SampleResultAnalysisDropCard"); + } } diff --git a/src/main/java/com/iflytop/a800/model/MdbIdChip.java b/src/main/java/com/iflytop/a800/model/MdbIdChip.java new file mode 100644 index 0000000..bcae114 --- /dev/null +++ b/src/main/java/com/iflytop/a800/model/MdbIdChip.java @@ -0,0 +1,8 @@ +package com.iflytop.a800.model; +public class MdbIdChip { + public Integer peakCount; + public Integer itemCount; + public String items; + public Integer scanPeakCount; + public Integer scanType; +} diff --git a/src/main/java/com/iflytop/a800/model/MdbProject.java b/src/main/java/com/iflytop/a800/model/MdbProject.java new file mode 100644 index 0000000..1fa7e92 --- /dev/null +++ b/src/main/java/com/iflytop/a800/model/MdbProject.java @@ -0,0 +1,15 @@ +package com.iflytop.a800.model; +import com.iflytop.uf.UfActiveRecord; +import com.iflytop.uf.UfActiveRecordField; +public class MdbProject extends UfActiveRecord { + @UfActiveRecordField + public String name; + + @UfActiveRecordField + public String steps; + + // get table name + public static String getTableName() { + return "app_projects"; + } +} diff --git a/src/main/java/com/iflytop/a800/resource/TestCard.java b/src/main/java/com/iflytop/a800/resource/TestCard.java new file mode 100644 index 0000000..3ed7164 --- /dev/null +++ b/src/main/java/com/iflytop/a800/resource/TestCard.java @@ -0,0 +1,4 @@ +package com.iflytop.a800.resource; +public class TestCard { + public Integer boxIndex; +} diff --git a/src/main/java/com/iflytop/a800/resource/TestCardManager.java b/src/main/java/com/iflytop/a800/resource/TestCardManager.java new file mode 100644 index 0000000..14828ca --- /dev/null +++ b/src/main/java/com/iflytop/a800/resource/TestCardManager.java @@ -0,0 +1,8 @@ +package com.iflytop.a800.resource; +public class TestCardManager { + public TestCard alloc() { + var card = new TestCard(); + card.boxIndex = 0; + return card; + } +} diff --git a/src/main/java/com/iflytop/a800/resource/TestTube.java b/src/main/java/com/iflytop/a800/resource/TestTube.java index 3fe02df..44f2cab 100644 --- a/src/main/java/com/iflytop/a800/resource/TestTube.java +++ b/src/main/java/com/iflytop/a800/resource/TestTube.java @@ -4,7 +4,9 @@ public class TestTube { // is tube existed public Boolean isExisted = false; // tube type - public Number type; + public String type; // tube bar code public String barCode; + // tube index + public Integer index; } diff --git a/src/main/java/com/iflytop/a800/resource/TestTubeRack.java b/src/main/java/com/iflytop/a800/resource/TestTubeRack.java index 10c5aa9..281062d 100644 --- a/src/main/java/com/iflytop/a800/resource/TestTubeRack.java +++ b/src/main/java/com/iflytop/a800/resource/TestTubeRack.java @@ -2,7 +2,7 @@ package com.iflytop.a800.resource; import java.util.List; public class TestTubeRack { // tube rack type - public Number type; + public String type; // list of test tubes public List tubes; } diff --git a/src/main/java/com/iflytop/a800/task/TubeRackTask.java b/src/main/java/com/iflytop/a800/task/TubeRackTask.java index bfecdcb..7e52fe2 100644 --- a/src/main/java/com/iflytop/a800/task/TubeRackTask.java +++ b/src/main/java/com/iflytop/a800/task/TubeRackTask.java @@ -34,13 +34,13 @@ public class TubeRackTask extends TaskBase { this.samplingTubes.add(tube); tube.type = tubeRack.type; - if ( !tube.type.equals(1) ) { + if ( !tube.type.equals("wb") ) { continue; } Boolean isWb5ml = feeder.readIsWb5ml(i); if ( isWb5ml ) { - tube.type = 2; + tube.type = "wb5ml"; } tube.barCode = feeder.readBarCode(i); diff --git a/src/main/java/com/iflytop/a800/task/TubeTestTask.java b/src/main/java/com/iflytop/a800/task/TubeTestTask.java index f1c37df..a31a801 100644 --- a/src/main/java/com/iflytop/a800/task/TubeTestTask.java +++ b/src/main/java/com/iflytop/a800/task/TubeTestTask.java @@ -1,25 +1,89 @@ package com.iflytop.a800.task; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.iflytop.a800.TaskBase; -import com.iflytop.a800.resource.TestTube; -import com.iflytop.a800.resource.TestTubeRack; +import com.iflytop.a800.device.Device; +import com.iflytop.a800.model.MdbIdChip; +import com.iflytop.a800.model.MdbProject; +import com.iflytop.a800.resource.*; +import com.iflytop.a800.utils.ScanResultAnalysisAlgo; import com.iflytop.uf.UfCmdSnippetExecutor; +import com.iflytop.uf.util.UfCommon; +import com.iflytop.uf.util.UfJsonHelper; + +import java.util.*; + public class TubeTestTask extends TaskBase { + // 测试项目 + public MdbProject project; public TestTubeRack tubeRack; public TestTube tube; public String status; + // 缓冲液试管 + private BufferTube bufferTube; + // 大容量缓冲液试管 + private LargeBufferTube largeBufferTube; + // 测试卡 + private TestCard testCard; + // id chip + private MdbIdChip idChip; + + // 任务准备 + public void prepare() { + var device = Device.getInstance(); + this.bufferTube = device.bufferTube.alloc(); + this.largeBufferTube = device.largeBufferTube.getTube(); + this.testCard = device.testCard.alloc(); + this.idChip = new MdbIdChip(); + this.idChip.peakCount = 3; + this.idChip.itemCount = 1; + this.idChip.scanPeakCount = 3; + this.idChip.items = UfJsonHelper.objectToJson(List.of(Map.of( + "item", "hsCRP", + "isPiecewiseFunction", false, + "nonPiecewiseFunction", Map.of( + "xValueSource", 1, + "serum", Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0), + "wb", Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0) + ) + ))); + } + @Override public void run() { - this.shake(); - this.uncap(); - this.sampling(); - this.cap(); - this.incubate(); - this.scan(); + this.prepare(); + + ObjectMapper jsonMapper = new ObjectMapper(); + JsonNode stepsJsonTree = null; + try { + stepsJsonTree = jsonMapper.readTree(this.project.steps); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + for ( JsonNode stepNode : stepsJsonTree ) { + var stepEnable = stepNode.get("enable").asBoolean(); + if ( !stepEnable ) { + continue; + } + + var stepAction = stepNode.get("action").asText(); + switch ( stepAction ) { +// case "shaking" : this.shake(stepNode); break; + case "puncture" : this.executeStepPuncture(stepNode); break; + case "aspirate" : this.executeStepAspirate(stepNode); break; + case "mixing" : this.executeStepMixing(stepNode); break; + case "drop-tip" : this.executeStepDropTip(stepNode); break; + case "incubate" : this.executeStepIncubate(stepNode); break; + case "analysis" : this.executeStepAnalysis(stepNode); break; + } + } } // 摇匀 - private void shake() { + private void shake( JsonNode stepNode ) { UfCmdSnippetExecutor.execute("SampleTestShake"); } @@ -28,25 +92,206 @@ public class TubeTestTask extends TaskBase { UfCmdSnippetExecutor.execute("SampleTestUnCap"); } - // 取样 - private void sampling() { - UfCmdSnippetExecutor.execute("SampleTestSamplingFromWb5ml"); - UfCmdSnippetExecutor.execute("SampleTestSamplingFromBufferTube"); - UfCmdSnippetExecutor.execute("SampleTestSamplingFromBufferTube"); + // 穿孔 + private void executeStepPuncture( JsonNode stepNode ) { + Device device = Device.getInstance(); + var pipette = device.pipette; + pipette.tipPickUp(); + pipette.puncture(this.bufferTube); + } + + // 吸液 + private void executeStepAspirate( JsonNode stepNode ) { + Device device = Device.getInstance(); + var pipette = device.pipette; + pipette.tipPickUp(); + + String source = stepNode.get("source").asText(); + Integer volume = stepNode.get("volume").asInt(); + if ( "LargeBufferTube".equals(source) ) { + pipette.aspirateFromLargeBufferTube(this.largeBufferTube, volume); + } else if ( "Sample".equals(source) && "EmergencyTube".equals(this.tube.type) ) { + pipette.aspirateFromEmergencyTube(this.tube.index, volume); + } else if ( "Sample".equals(source) && "SampleEpp0_5Tube".equals(this.tube.type) ) { + pipette.aspirateFromSampleEpp0_5(this.tube.index, volume); + } else if ( "Sample".equals(source) && "SampleEpp1_5Tube".equals(this.tube.type) ) { + pipette.aspirateFromSampleEpp1_5(this.tube.index, volume); + } else if ( "Sample".equals(source) && "SampleWholeBlood5mlTube".equals(this.tube.type) ) { + pipette.aspirateFromWholeBlood5ml(this.tube.index, volume); + } else if ( "Sample".equals(source) && "SampleWholeBlood3mlTube".equals(this.tube.type) ) { + pipette.aspirateFromWholeBlood3ml(this.tube.index, volume); + } else { + throw new RuntimeException(String.format("无效的取样源 : %s/%s",source,this.tube.type)); + } + pipette.dispense(this.bufferTube); + } + + // 混匀 + private void executeStepMixing( JsonNode stepNode ) { + Device device = Device.getInstance(); + var pipette = device.pipette; + var mixCount = stepNode.get("count").asInt(); + var mixVolume = stepNode.get("volume").asInt(); + pipette.mix(this.bufferTube, mixCount, mixVolume); } - // 盖回 - private void cap() { - UfCmdSnippetExecutor.execute("SampleTestCap"); + // 丢弃吸头 + private void executeStepDropTip( JsonNode stepNode ) { + Device device = Device.getInstance(); + var pipette = device.pipette; + pipette.tipDrop(); } // 孵育 - private void incubate() { - UfCmdSnippetExecutor.execute("SampleTestIncubate"); + private void executeStepIncubate( JsonNode stepNode ) { + Device device = Device.getInstance(); + device.incubator.pushNewCard(this.testCard.boxIndex); + + var pipette = device.pipette; + pipette.tipPickUp(); + + Integer volume = stepNode.get("volume").asInt(); + pipette.aspirateFromBufferTubeAndDispenseToTestCard(this.bufferTube, volume); + + pipette.tipDrop(); + + Integer duration = stepNode.get("duration").asInt(); + UfCommon.delay(duration); } // 扫描 - private void scan() { - UfCmdSnippetExecutor.execute("SampleTestScanResult"); + private void executeStepAnalysis( JsonNode stepNode ) { + // 扫描 + Device device = Device.getInstance(); +// device.incubator.exitToScanner(); + var scanner = device.scanner; + scanner.scanTypeF(); + var scanResult = scanner.readResult(); + scanner.dropCard(); + + // 计算 + var algo = new ScanResultAnalysisAlgo(); + var algoResult = algo.calculate(scanResult, this.idChip.peakCount); + + // 处理 + String sampleType = "WB"; // @TODO : 这里要从样本中取 ~~~~ + List> results = new ArrayList<>(); + var projects = UfJsonHelper.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(algoResult, xValueSource, itemName); + funcInfo = func.get("serum"); + if ("WB".equals(sampleType)) { + funcInfo = func.get("wb"); + } + } else { // 分段函数 + var func = project.get("piecewiseFunction"); + var piecewiseValueSrc = func.get("piecewiseValueSrc").asInt(); + var piecewiseValueSrcValue = this.calculateRatio(algoResult, piecewiseValueSrc, itemName); + var piecewiseValue = func.get("piecewiseValue").asDouble(); + if ( piecewiseValueSrcValue > piecewiseValue ) { // 高浓度 + var highXValueSource = func.get("highXValueSource").asInt(); + valueX = this.calculateRatio(algoResult, highXValueSource, itemName); + funcInfo = func.get("serumHigh"); + if ("WB".equals(sampleType)) { + funcInfo = func.get("wbHigh"); + } + } else { // 低浓度 + var lowXValueSource = func.get("lowXValueSource").asInt(); + valueX = this.calculateRatio(algoResult, lowXValueSource, itemName); + funcInfo = func.get("serumLow"); + if ("WB".equals(sampleType)) { + funcInfo = func.get("wbLow"); + } + } + } + + 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; + + // @TODO : 单位也要从配置中读取 + results.add(Map.of("name",itemName,"value",value,"unit","xx/xx")); + } + + String testResult = UfJsonHelper.objectToJson(results); + System.out.println(testResult); + } + + /** + * @link https://iflytop1.feishu.cn/docx/UnRtdSG4qouMTaxzRb2cjiTTnZe + * @link https://iflytop1.feishu.cn/docx/A0CHdQL6OoTTCSx8MB7cQKEjnSc + * @param result - algo result + * @param xSource - x source + * @return double - value of ratio + */ + private double calculateRatio(ScanResultAnalysisAlgo.AlgoResult result, int xSource, String itenName) { + // 计算 ratio + Map 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, 1) ) { // 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, 2) ) { // 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); + } } } diff --git a/src/main/java/com/iflytop/a800/utils/ScanResultAnalysisAlgo.java b/src/main/java/com/iflytop/a800/utils/ScanResultAnalysisAlgo.java new file mode 100644 index 0000000..2282e5d --- /dev/null +++ b/src/main/java/com/iflytop/a800/utils/ScanResultAnalysisAlgo.java @@ -0,0 +1,29 @@ +package com.iflytop.a800.utils; +public class ScanResultAnalysisAlgo { + 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 { + String osName = System.getProperty("os.name").toLowerCase(); + if ( osName.contains("win") ) { + System.load("D:/Sige5193/boditech-a800/lib-algo/x64/Debug/boditech-opt-algo-java-lib.dll"); + } else { + System.load("/app-java/algo/libalgo.so"); + } + } + + public native AlgoResult calculate(float[] data, int peakCount); +}