diff --git a/app.db b/app.db index 3b6fffa..34d40e2 100644 Binary files a/app.db and b/app.db differ diff --git a/src/main/java/a8k/hardware/A8kModParamInitializer.java b/src/main/java/a8k/hardware/A8kModParamInitializer.java index 23afc8d..d3ccd58 100644 --- a/src/main/java/a8k/hardware/A8kModParamInitializer.java +++ b/src/main/java/a8k/hardware/A8kModParamInitializer.java @@ -1,5 +1,7 @@ package a8k.hardware; +import a8k.service.app.devicedriver.basectrl.PipetteCtrlModule; +import a8k.service.app.devicedriver.basectrl.type.PipetteRegIndex; import a8k.service.bases.AppEventBusService; import a8k.service.bases.appevent.A8kCanBusOnConnectEvent; import a8k.service.bases.appevent.AppEvent; @@ -15,14 +17,17 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component -public class A8kModParamInitializer { +public class A8kModParamInitializer { static Logger logger = LoggerFactory.getLogger(A8kModParamInitializer.class); @Resource AppEventBusService eventBus; @Resource - A8kCanBusService canBus; + A8kCanBusService canBus; + @Resource + PipetteCtrlModule pipetteCtrlModule; + @Resource A8kModCustomParamMgr customParamMgr; @@ -54,7 +59,7 @@ public class A8kModParamInitializer { canBus.moduleSetReg(MId.PlatesBoxPusherM, RegIndex.kret_step_motor_pos_devi_tolerance, 5); canBus.moduleSetReg(MId.OptModPullM, RegIndex.kret_step_motor_pos_devi_tolerance, 5); canBus.moduleSetReg(MId.OptModScannerM, RegIndex.kret_step_motor_pos_devi_tolerance, 5); -// canBus.moduleSetReg(MId.PipetteModZM, RegIndex.kret_step_motor_pos_devi_tolerance, 20); + pipetteCtrlModule.setReg(PipetteRegIndex.kreg_pipette_zm_pos_devi_tolerance, 20); canBus.moduleSetReg(MId.IncubatorRotateCtrlM, RegIndex.kret_step_motor_pos_devi_tolerance, 5); canBus.moduleSetReg(MId.FeedingModXM, RegIndex.kret_step_motor_io_trigger_append_distance, 10); @@ -65,7 +70,7 @@ public class A8kModParamInitializer { canBus.moduleSetReg(MId.PlatesBoxPusherM, RegIndex.kret_step_motor_io_trigger_append_distance, 3); canBus.moduleSetReg(MId.OptModPullM, RegIndex.kret_step_motor_io_trigger_append_distance, 10); canBus.moduleSetReg(MId.OptModScannerM, RegIndex.kret_step_motor_io_trigger_append_distance, 10); -// canBus.moduleSetReg(MId.PipetteModZM, RegIndex.kret_step_motor_io_trigger_append_distance, 10); + pipetteCtrlModule.setReg(PipetteRegIndex.kreg_pipette_zm_io_trigger_append_distance, 10); canBus.moduleSetReg(MId.IncubatorRotateCtrlM, RegIndex.kret_step_motor_io_trigger_append_distance, 10); canBus.moduleSetReg(MId.ShakeModGripperYSV, RegIndex.kreg_mini_servo_limit_torque, 700); @@ -81,6 +86,7 @@ public class A8kModParamInitializer { canBus.moduleSetReg(MId.PlatesBoxYM, RegIndex.kreg_step_motor_default_velocity, 1800); canBus.moduleSetReg(MId.PlatesBoxYM, RegIndex.kreg_step_motor_irun, 25); + pipetteCtrlModule.setReg(PipetteRegIndex.kreg_pipette_zm_default_velocity, 1500); //Hbot-丢步容忍距离 canBus.moduleSetReg(MId.HbotM, RegIndex.kreg_xyrobot_pos_devi_tolerance, 5); @@ -98,11 +104,7 @@ public class A8kModParamInitializer { canBus.moduleSetReg(MId.HbotM, RegIndex.kreg_xyrobot_vstop, 1); canBus.moduleSetReg(MId.HbotM, RegIndex.kreg_xyrobot_tzerowait, 300); -// canBus.moduleSetReg(MId.PipetteModZM, RegIndex.kreg_step_motor_default_velocity, 1500); -// canBus.moduleSetReg(MId.HbotM, RegIndex.kreg_xyrobot_run_to_zero_speed, 300); -// canBus.moduleSetReg(MId.HbotM, RegIndex.kreg_xyrobot_one_circle_pulse, 7215); - // canBus.moduleSetReg(MId.OptModPullM, RegIndex.kreg_step_motor_run_to_zero_speed, 500); } diff --git a/src/main/java/a8k/service/app/devicedriver/calibration/HbotLittleBottleConsumableCalibration.java b/src/main/java/a8k/service/app/devicedriver/calibration/HbotLittleBottleConsumableCalibration.java index 781907e..2a77595 100644 --- a/src/main/java/a8k/service/app/devicedriver/calibration/HbotLittleBottleConsumableCalibration.java +++ b/src/main/java/a8k/service/app/devicedriver/calibration/HbotLittleBottleConsumableCalibration.java @@ -13,7 +13,10 @@ import a8k.type.*; import a8k.type.cfg.Pos2d; import a8k.type.cfg.Pos3d; import a8k.type.exception.AppException; +import a8k.utils.ZJsonHelper; import a8k.utils.ZSimplAlgo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.annotation.Resource; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; @@ -57,9 +60,9 @@ public class HbotLittleBottleConsumableCalibration { } - @ExtApiFn(name = "获取所有坐标", group = "基础", order = 1) - public Object getPoss() throws AppException { - return hbotConsumablePosMgr.getParams(); + @ExtApiFn(name = "获取坐标", group = "基础", order = 1) + public Object getPoss(LittleBottleConsumableType type) throws AppException { + return hbotConsumablePosMgr.getLittleBottleConsumablePosInfo(type); } @@ -84,9 +87,10 @@ public class HbotLittleBottleConsumableCalibration { } - LittleBottleConsumableType littleBottleConsumableType = LittleBottleConsumableType.BufferSolution; - List littleBottleConsumableRefPoint; + LittleBottleConsumableType littleBottleConsumableType = LittleBottleConsumableType.BufferSolution; + List littleBottleConsumableRefPoint = new java.util.ArrayList<>(); LittleBottleConsumablePosInfo littleBottleConsumablePosInfo; + List zpos = new java.util.ArrayList<>(); @ExtApiFn(name = "开始标定小瓶坐标", group = "标定小瓶坐标", order = 30) @@ -94,6 +98,7 @@ public class HbotLittleBottleConsumableCalibration { littleBottleConsumablePosInfo = null; littleBottleConsumableRefPoint = new java.util.ArrayList<>(); littleBottleConsumableType = type; + zpos.clear(); moveToZero(); disableModule(); } @@ -107,10 +112,23 @@ public class HbotLittleBottleConsumableCalibration { LittleBottleConsumableRefPoint littleBufferRefPoint = new LittleBottleConsumableRefPoint(group, off0To24, new Pos3d(xypos.x, xypos.y, z)); this.littleBottleConsumableRefPoint.add(littleBufferRefPoint); disableModule(); - return computeLittleBottlePosInfo(); + ObjectNode node = ZJsonHelper.createObjectNode(); + node.put("newPoint", ZJsonHelper.createObjectNode(littleBufferRefPoint)); + node.put("computeResult", ZJsonHelper.createObjectNode(computeLittleBottlePosInfo())); + return node; } - @ExtApiFn(name = "删除上一个小瓶参考点", group = "标定小瓶坐标", order = 32) + @ExtApiFn(name = "添加Z轴坐标点", group = "标定小瓶坐标", order = 32) + public Object addLittleBottleGroupRefPoint() throws AppException { + enableModule(); + pipetteCtrlModule.zMotorMeasureDistance(); + Integer z = pipetteCtrlModule.zMotorReadMeasureDistanceResult(); + zpos.add(z); + disableModule(); + return z; + } + + @ExtApiFn(name = "计算坐标", group = "标定小瓶坐标", order = 32) public LittleBottleConsumablePosInfo computeLittleBottlePosInfo() { //计算y0,所有group==0,1,2,同时0<=off<5的点,求平均 Double[] y = new Double[2]; @@ -119,7 +137,16 @@ public class HbotLittleBottleConsumableCalibration { Double[] dys = new Double[6]; Double dx; Double dy; - Double z; + double z = 0.0; + + LittleBottleConsumableRefPoint[] gx_00 = new LittleBottleConsumableRefPoint[6]; + + gx_00[0] = littleBottleConsumableRefPoint.stream().filter(point -> point.group == ConsumableGroup.GROUP0 && point.index == 0).findFirst().orElse(null); + gx_00[1] = littleBottleConsumableRefPoint.stream().filter(point -> point.group == ConsumableGroup.GROUP1 && point.index == 0).findFirst().orElse(null); + gx_00[2] = littleBottleConsumableRefPoint.stream().filter(point -> point.group == ConsumableGroup.GROUP2 && point.index == 0).findFirst().orElse(null); + gx_00[3] = littleBottleConsumableRefPoint.stream().filter(point -> point.group == ConsumableGroup.GROUP3 && point.index == 0).findFirst().orElse(null); + gx_00[4] = littleBottleConsumableRefPoint.stream().filter(point -> point.group == ConsumableGroup.GROUP4 && point.index == 0).findFirst().orElse(null); + gx_00[5] = littleBottleConsumableRefPoint.stream().filter(point -> point.group == ConsumableGroup.GROUP5 && point.index == 0).findFirst().orElse(null); y[0] = ZSimplAlgo.computeAverage( littleBottleConsumableRefPoint.stream().filter(point -> point.group.ordinal() < 3 && point.index < 5). @@ -131,89 +158,96 @@ public class HbotLittleBottleConsumableCalibration { map(point -> point.pos.y).toList() ); - //计算y0,所有group==0,3,同时off==0,5,10,15,20的点,求y平均 + //计算x0,所有group==0,3,同时off==0,5,10,15,20的点,求y平均 x[0] = ZSimplAlgo.computeAverage( littleBottleConsumableRefPoint.stream().filter(point -> point.group.ordinal() % 3 == 0 && point.index % 5 == 0). - map(point -> point.pos.y).toList() + map(point -> point.pos.x).toList() ); - //计算y1,所有group==1,4,同时off==0,5,10,15,20的点,求y平均 + //计算x1,所有group==1,4,同时off==0,5,10,15,20的点,求y平均 x[1] = ZSimplAlgo.computeAverage( littleBottleConsumableRefPoint.stream().filter(point -> point.group.ordinal() % 3 == 1 && point.index % 5 == 0). - map(point -> point.pos.y).toList() + map(point -> point.pos.x).toList() ); - //计算y2 + //计算x2 x[2] = ZSimplAlgo.computeAverage( littleBottleConsumableRefPoint.stream().filter(point -> point.group.ordinal() % 3 == 2 && point.index % 5 == 0). - map(point -> point.pos.y).toList() - ); - // - z = ZSimplAlgo.computeAverage( - littleBottleConsumableRefPoint.stream().map(point -> point.pos.z).toList() + map(point -> point.pos.x).toList() ); + z = ZSimplAlgo.computeAverage(zpos); //计算d0x,所有 非0,5,10,15,20的点,到x0的距离/偏移差值,求平均 for (int i = 0; i < 6; i++) { int finalI = i; - dxs[i] = ZSimplAlgo.computeAverage( + + if (gx_00[i] == null) { + continue; + } + Double val = ZSimplAlgo.computeAverage( littleBottleConsumableRefPoint.stream().filter(point -> point.group.ordinal() == finalI && point.getColOff() != 0). - map(point -> point.pos.x - x[finalI % 3] / (point.getColOff())).toList() + map(point -> (point.pos.x - gx_00[finalI].pos.x) / (point.getColOff())).toList() ); + dxs[i] = val; } dx = ZSimplAlgo.computeAverage(dxs); //计算dy, for (int i = 0; i < 6; i++) { int finalI = i; + if (gx_00[i] == null) + continue; + dys[i] = ZSimplAlgo.computeAverage( littleBottleConsumableRefPoint.stream().filter(point -> point.group.ordinal() == finalI && point.getRowOff() != 0). - map(point -> point.pos.y - y[finalI / 3] / (point.getRowOff())).toList() + map(point -> (point.pos.y - gx_00[finalI].pos.y) / (point.getRowOff())).toList() ); } dy = ZSimplAlgo.computeAverage(dys); littleBottleConsumablePosInfo = new LittleBottleConsumablePosInfo(); - littleBottleConsumablePosInfo.g0_000 = new Pos3d(x[0].intValue(), y[0].intValue(), z.intValue()); - littleBottleConsumablePosInfo.g1_000 = new Pos3d(x[1].intValue(), y[0].intValue(), z.intValue()); - littleBottleConsumablePosInfo.g2_000 = new Pos3d(x[2].intValue(), y[0].intValue(), z.intValue()); - littleBottleConsumablePosInfo.g3_000 = new Pos3d(x[0].intValue(), y[1].intValue(), z.intValue()); - littleBottleConsumablePosInfo.g4_000 = new Pos3d(x[1].intValue(), y[1].intValue(), z.intValue()); - littleBottleConsumablePosInfo.g5_000 = new Pos3d(x[2].intValue(), y[1].intValue(), z.intValue()); + littleBottleConsumablePosInfo.g0_000 = new Pos3d(x[0].intValue(), y[0].intValue(), (int) z); + littleBottleConsumablePosInfo.g1_000 = new Pos3d(x[1].intValue(), y[0].intValue(), (int) z); + littleBottleConsumablePosInfo.g2_000 = new Pos3d(x[2].intValue(), y[0].intValue(), (int) z); + littleBottleConsumablePosInfo.g3_000 = new Pos3d(x[0].intValue(), y[1].intValue(), (int) z); + littleBottleConsumablePosInfo.g4_000 = new Pos3d(x[1].intValue(), y[1].intValue(), (int) z); + littleBottleConsumablePosInfo.g5_000 = new Pos3d(x[2].intValue(), y[1].intValue(), (int) z); littleBottleConsumablePosInfo.dx = dx; littleBottleConsumablePosInfo.dy = dy; return littleBottleConsumablePosInfo; } - @ExtApiFn(name = "保存小瓶坐标", group = "标定小瓶坐标", order = 33) + @ExtApiFn(name = "保存计算结果", group = "标定小瓶坐标", order = 33) public void saveLittleBottleConsumablePosInfo() throws AppException { hbotConsumablePosMgr.setLittleBottleConsumablePosInfo(littleBottleConsumableType, littleBottleConsumablePosInfo); } + @ExtApiFn(name = "读取所有参考点", group = "标定小瓶坐标", order = 33) + public Object readAllRefPoint() throws AppException { + return littleBottleConsumableRefPoint; + } + // // 校验 // @ExtApiFn(name = "校验小瓶缓冲液坐标", group = "校验", order = 301) - public void testMoveToLittleBufferPos() throws AppException { + public void testMoveToLittleBufferPos(ConsumableGroup group) throws AppException { resetStopFlag(); enableModule(); - for (ConsumableGroup group : ConsumableGroup.values()) { - for (int i = 0; i < AppConstant.CONSUMABLE_NUM; i++) { - hbotControler.moveToLittleBufferPos(group, i); - if (checkStopFlag()) - return; - } + for (int i = 0; i < AppConstant.CONSUMABLE_NUM; i++) { + hbotControler.moveToLittleBufferPos(group, i); + pipetteCtrlModule.zMotorMoveToZeroPointQuickBlock(); + if (checkStopFlag()) + return; } - } @ExtApiFn(name = "校验探测物质坐标", group = "校验", order = 302) - public void testMoveToProbeSubstancePos() throws AppException { + public void testMoveToProbeSubstancePos(ConsumableGroup group) throws AppException { resetStopFlag(); enableModule(); - for (ConsumableGroup group : ConsumableGroup.values()) { - for (int i = 0; i < AppConstant.CONSUMABLE_NUM; i++) { - hbotControler.moveToProbeSubstancePos(group, i); - if (checkStopFlag()) - return; - } + for (int i = 0; i < AppConstant.CONSUMABLE_NUM; i++) { + hbotControler.moveToProbeSubstancePos(group, i); + pipetteCtrlModule.zMotorMoveToZeroPointQuickBlock(); + if (checkStopFlag()) + return; } } diff --git a/src/main/java/a8k/service/app/devicedriver/pos/HbotConsumablePosMgr.java b/src/main/java/a8k/service/app/devicedriver/pos/HbotConsumablePosMgr.java index 1ff2576..f935179 100644 --- a/src/main/java/a8k/service/app/devicedriver/pos/HbotConsumablePosMgr.java +++ b/src/main/java/a8k/service/app/devicedriver/pos/HbotConsumablePosMgr.java @@ -62,6 +62,32 @@ public class HbotConsumablePosMgr { posReader.updatePos(HbotConsumablePosParam.TipGroup2_SpaceingX, 0.0); posReader.updatePos(HbotConsumablePosParam.TipGroup2_SpaceingY, 0.0); + + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroup0_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroup1_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroup2_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroup3_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroup4_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroup5_000Pos, new Pos3d()); + + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroupDX, 0.0); + posReader.updatePos(HbotConsumablePosParam.LittleBufferGroupDY, 0.0); + + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceGroup0_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceGroup1_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceGroup2_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceGroup3_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceGroup4_000Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceGroup5_000Pos, new Pos3d()); + + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceDX, 0.0); + posReader.updatePos(HbotConsumablePosParam.ProbeSubstanceDY, 0.0); + + posReader.updatePos(HbotConsumablePosParam.LargeBuffer_0Pos, new Pos3d()); + posReader.updatePos(HbotConsumablePosParam.LargeBuffer_DX, 0.0); + posReader.updatePos(HbotConsumablePosParam.LargeBuffer_DY, 0.0); + + } public List getParams() { @@ -107,6 +133,40 @@ public class HbotConsumablePosMgr { posReader.updatePos(tipDyPos, dy); } + public LittleBottleConsumablePosInfo getLittleBufferGroupPosInfo() { + LittleBottleConsumablePosInfo info = new LittleBottleConsumablePosInfo(); + info.g0_000 = posReader.getPos(HbotConsumablePosParam.LittleBufferGroup0_000Pos, Pos3d.class); + info.g1_000 = posReader.getPos(HbotConsumablePosParam.LittleBufferGroup1_000Pos, Pos3d.class); + info.g2_000 = posReader.getPos(HbotConsumablePosParam.LittleBufferGroup2_000Pos, Pos3d.class); + info.g3_000 = posReader.getPos(HbotConsumablePosParam.LittleBufferGroup3_000Pos, Pos3d.class); + info.g4_000 = posReader.getPos(HbotConsumablePosParam.LittleBufferGroup4_000Pos, Pos3d.class); + info.g5_000 = posReader.getPos(HbotConsumablePosParam.LittleBufferGroup5_000Pos, Pos3d.class); + info.dx = posReader.getPos(HbotConsumablePosParam.LittleBufferGroupDX, Double.class); + info.dy = posReader.getPos(HbotConsumablePosParam.LittleBufferGroupDY, Double.class); + return info; + } + + public LittleBottleConsumablePosInfo getProbeSubstanceGroupPosInfo() { + LittleBottleConsumablePosInfo info = new LittleBottleConsumablePosInfo(); + info.g0_000 = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceGroup0_000Pos, Pos3d.class); + info.g1_000 = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceGroup1_000Pos, Pos3d.class); + info.g2_000 = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceGroup2_000Pos, Pos3d.class); + info.g3_000 = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceGroup3_000Pos, Pos3d.class); + info.g4_000 = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceGroup4_000Pos, Pos3d.class); + info.g5_000 = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceGroup5_000Pos, Pos3d.class); + info.dx = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceDX, Double.class); + info.dy = posReader.getPos(HbotConsumablePosParam.ProbeSubstanceDY, Double.class); + return info; + } + + public LittleBottleConsumablePosInfo getLittleBottleConsumablePosInfo(LittleBottleConsumableType type) { + if (type.equals(LittleBottleConsumableType.BufferSolution)) { + return getLittleBufferGroupPosInfo(); + } else { + return getProbeSubstanceGroupPosInfo(); + } + } + public void setLittleBottleConsumablePosInfo(LittleBottleConsumableType type, LittleBottleConsumablePosInfo info) { HbotConsumablePosParam group0_000Pos; HbotConsumablePosParam group1_000Pos; diff --git a/src/main/java/a8k/type/LittleBottleConsumableRefPoint.java b/src/main/java/a8k/type/LittleBottleConsumableRefPoint.java index 655f681..8cdd267 100644 --- a/src/main/java/a8k/type/LittleBottleConsumableRefPoint.java +++ b/src/main/java/a8k/type/LittleBottleConsumableRefPoint.java @@ -20,6 +20,6 @@ public class LittleBottleConsumableRefPoint { } public Integer getColOff() { - return index % AppConstant.CONSUMABLE_COL_NUM; + return index % AppConstant.CONSUMABLE_ROW_NUM; } } diff --git a/src/main/java/a8k/utils/ZJsonHelper.java b/src/main/java/a8k/utils/ZJsonHelper.java index 50aaa0f..8dfe5c6 100644 --- a/src/main/java/a8k/utils/ZJsonHelper.java +++ b/src/main/java/a8k/utils/ZJsonHelper.java @@ -20,6 +20,7 @@ public class ZJsonHelper { return new ObjectMapper().createObjectNode(); } + public static ObjectNode createObjectNode(Object obj) { ObjectMapper ObjectMapper = new ObjectMapper(); return ObjectMapper.valueToTree(obj); diff --git a/src/main/java/a8k/utils/ZSimplAlgo.java b/src/main/java/a8k/utils/ZSimplAlgo.java index 0a90b97..f13f356 100644 --- a/src/main/java/a8k/utils/ZSimplAlgo.java +++ b/src/main/java/a8k/utils/ZSimplAlgo.java @@ -5,28 +5,46 @@ import java.util.List; public class ZSimplAlgo { public static Double computeAverage(List data) { double sum = 0.0; + int cnt = 0; for (var datum : data) { + if(datum == null) { + continue; + } + if (datum instanceof Double) { + if (Double.isNaN((Double) datum) || Double.isInfinite((Double) datum)) { + continue; + } sum += (Double) datum; } else if (datum instanceof Integer) { sum += (Integer) datum; } else if (datum instanceof Long) { sum += (Long) datum; } else if (datum instanceof Float) { + if (Float.isNaN((Float) datum) || Float.isInfinite((Float) datum)) { + continue; + } sum += (Float) datum; } else { throw new IllegalArgumentException("Unsupported type: " + datum.getClass().getName()); } + cnt++; + } return sum / data.size(); } public static Double computeAverage(Double[] data) { - Double sum = 0.0; + Double sum = 0.0; + Integer cnt = 0; for (Double datum : data) { + if (datum == null || datum.isNaN() || datum.isInfinite()) { + continue; + } sum += datum; + cnt++; } - return sum / data.length; + return sum / cnt; } }