general.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // ignore_for_file: constant_identifier_names
  2. import 'dart:math' as math;
  3. import 'package:fis_measure/configs/patient.dart';
  4. import 'package:fis_measure/interfaces/date_types/point.dart';
  5. import 'package:fis_measure/interfaces/enums/species.dart';
  6. class GeneralFormulas {
  7. GeneralFormulas._();
  8. static const double VolumeCofficient = math.pi / 6.0;
  9. static final IGeneralFormulaStrategy _singleton =
  10. GlobalPatientConfig.speciesType == SpeciesType.mouse
  11. ? BaseGeneralFormulas()
  12. : AnimalsGeneralFormulas();
  13. static double volumeTwoLine(double d1, double d2,
  14. [bool useBigValue = true]) =>
  15. _singleton.volumeTwoLine(d1, d2, useBigValue);
  16. static double volumeWithCoefficient(
  17. double d1, double d2, double d3, double coefficient) =>
  18. _singleton.volumeWithCoefficient(d1, d2, d3, coefficient);
  19. static double countStenosis(double d1, double d2) =>
  20. _singleton.countStenosis(d1, d2);
  21. static bool doubleAlmostEquals(double num1, double num2,
  22. [double precision = 0.000001]) =>
  23. _singleton.doubleAlmostEquals(num1, num2, precision);
  24. static double distance2Line(DPoint pt1, DPoint pt2, DPoint pt) =>
  25. _singleton.distance2Line(pt1, pt2, pt);
  26. static double countSlope(DPoint p1, DPoint p2) =>
  27. _singleton.countSlope(p1, p2);
  28. static double countEnvelopeTime(List<DPoint> points) =>
  29. _singleton.countEnvelopeTime(points);
  30. static double countPressure(double v) => _singleton.countPressure(v);
  31. static double countRI(double ps, double mdEd) => _singleton.countRI(ps, mdEd);
  32. static double countMaxPG(double v1, double v2) =>
  33. _singleton.countMaxPG(v1, v2);
  34. static double countHR(double timeSpan) => _singleton.countHR(timeSpan);
  35. static List<double> countVTI(List<DPoint> tracePoints) =>
  36. _singleton.countVTI(tracePoints);
  37. static double svDiam(double diam, double vti) => _singleton.svDiam(diam, vti);
  38. static double csa(double diam) => _singleton.csa(diam);
  39. static double dpDtRatio(DPoint p1, DPoint p2) => _singleton.dpDtRatio(p1, p2);
  40. static double maxPG(double v1, double v2) => _singleton.maxPG(v1, v2);
  41. static double area(double d1, double d2) => _singleton.area(d1, d2);
  42. static double flowVolTAMAX(
  43. double flowArea, double tamean, double coefficient) =>
  44. _singleton.flowVolTAMAX(flowArea, tamean, coefficient);
  45. }
  46. abstract class IGeneralFormulaStrategy {
  47. double volumeTwoLine(double d1, double d2, [bool useBigValue = true]);
  48. double volumeWithCoefficient(
  49. double d1,
  50. double d2,
  51. double d3,
  52. double coefficient,
  53. );
  54. double countStenosis(double d1, double d2);
  55. bool doubleAlmostEquals(double num1, double num2,
  56. [double precision = 0.000001]);
  57. double distance2Line(DPoint pt1, DPoint pt2, DPoint pt);
  58. double countSlope(DPoint p1, DPoint p2);
  59. double countEnvelopeTime(List<DPoint> points);
  60. double countPressure(double v);
  61. double countRI(double ps, double mdEd);
  62. double countMaxPG(double v1, double v2);
  63. double countHR(double timeSpan);
  64. List<double> countVTI(
  65. List<DPoint> tracePoints,
  66. );
  67. double svDiam(double diam, double vti);
  68. double csa(double diam);
  69. double dpDtRatio(DPoint p1, DPoint p2);
  70. double maxPG(double v1, double v2);
  71. double area(double d1, double d2);
  72. double flowVolTAMAX(double flowArea, double tamean, double coefficient);
  73. }
  74. class BaseGeneralFormulas implements IGeneralFormulaStrategy {
  75. /// Volume:1/6 x π
  76. static const double VolumeCofficient = math.pi / 6.0;
  77. /// 1/6 x π x D1^2 x D2 (D1 > D2)
  78. ///
  79. /// 1/6 x π x D1 x D2^2 (D1 ≤ D2)
  80. ///
  81. /// return unit: cm³</returns>
  82. @override
  83. double volumeTwoLine(double d1, double d2, [bool useBigValue = true]) {
  84. double volume = 0;
  85. if (!doubleAlmostEquals(d1, 0) && !doubleAlmostEquals(d2, 0)) {
  86. if (useBigValue) {
  87. if (d1 > d2) {
  88. volume = d1 * d1 * d2 * math.pi / 6.0;
  89. } else {
  90. volume = d1 * d2 * d2 * math.pi / 6.0;
  91. }
  92. } else {
  93. if (d1 > d2) {
  94. volume = d2 * d2 * d1 * math.pi / 6.0;
  95. } else {
  96. volume = d1 * d1 * d2 * math.pi / 6.0;
  97. }
  98. }
  99. }
  100. return volume;
  101. }
  102. /// 带比率系数算体积
  103. ///
  104. /// [coefficient] 比率系数
  105. @override
  106. double volumeWithCoefficient(
  107. double d1,
  108. double d2,
  109. double d3,
  110. double coefficient,
  111. ) {
  112. double volume = 0;
  113. if (!doubleAlmostEquals(d1, 0) &&
  114. !doubleAlmostEquals(d2, 0) &&
  115. !doubleAlmostEquals(d3, 0)) {
  116. volume = d1 * d2 * d3 * coefficient;
  117. }
  118. return volume;
  119. }
  120. /// 狭窄率计算
  121. @override
  122. double countStenosis(double d1, double d2) {
  123. double residual;
  124. double lumen;
  125. if (d1 > d2) {
  126. residual = d2;
  127. lumen = d1;
  128. } else {
  129. residual = d1;
  130. lumen = d2;
  131. }
  132. return (1.0 - residual / lumen) * 100;
  133. }
  134. @override
  135. bool doubleAlmostEquals(double num1, double num2,
  136. [double precision = 0.000001]) {
  137. if (num1.isNaN && num2.isNaN) return true;
  138. return (num1 - num2).abs() <= precision;
  139. }
  140. /// 点到线的距离
  141. ///
  142. /// [pt1] 线端点1
  143. ///
  144. /// [pt2] 线端点2
  145. ///
  146. /// [pt] 点
  147. @override
  148. double distance2Line(DPoint pt1, DPoint pt2, DPoint pt) {
  149. double dis = 0;
  150. if (pt1.x == pt2.x) {
  151. dis = (pt.x - pt1.x).abs();
  152. return dis;
  153. }
  154. var lineK = (pt2.y - pt1.y) / (pt2.x - pt1.x);
  155. var lineC = (pt2.x * pt1.y - pt1.x * pt2.y) / (pt2.x - pt1.x);
  156. dis = (lineK * pt.x - pt.y + lineC).abs() / (math.sqrt(lineK * lineK + 1));
  157. return dis;
  158. }
  159. ///计算斜率
  160. @override
  161. double countSlope(DPoint p1, DPoint p2) {
  162. if (doubleAlmostEquals(p2.x, p1.x)) return 0;
  163. double vertical = p2.y - p1.y;
  164. double time = (p2.x - p1.x).abs();
  165. double slope = vertical / time;
  166. return slope;
  167. }
  168. ///计算包络时间
  169. ///Origin: Vinno.Modules.MeasureModule\Formulas\GeneralFormulas.cs [632:67]
  170. @override
  171. double countEnvelopeTime(List<DPoint> points) {
  172. if (points.length < 2) return 0;
  173. double startTime = double.maxFinite;
  174. double endTime = -double.maxFinite;
  175. for (var point in points) {
  176. if (point.x < startTime) {
  177. startTime = point.x;
  178. }
  179. if (point.x > endTime) {
  180. endTime = point.x;
  181. }
  182. }
  183. return (endTime - startTime).abs();
  184. }
  185. /// <summary>
  186. /// 4.0 x V^2
  187. /// </summary>
  188. /// <param name="v">Unit cm/s</param>
  189. /// <returns>Unit mmHg</returns>
  190. @override
  191. double countPressure(double v) {
  192. // The velocity is in cm/s, but it should be m/s in this formula, so divide it by 100.
  193. double pg = 4.0 * math.pow(v * 0.01, 2);
  194. return pg;
  195. }
  196. /// <summary>
  197. /// Resistivity Index 阻力指数
  198. /// <para>RI (ED)=(PS - ED)/PS </para>
  199. /// <para>RI (MD)=(PS - MD)/PS </para>
  200. /// </summary>
  201. /// <param name="ps">Unit cm/s</param>
  202. /// <param name="mdEd">Unit cm/s</param>
  203. @override
  204. double countRI(double ps, double mdEd) {
  205. ps = ps.abs();
  206. mdEd = mdEd.abs();
  207. double ri = (ps - mdEd) / ps;
  208. if (ri < 0) {
  209. return double.nan;
  210. }
  211. return ri;
  212. }
  213. /// <summary>
  214. /// 4.0 x |V1^2 - V2^2|
  215. /// </summary>
  216. /// <param name="v1">Unit cm/s</param>
  217. /// <param name="v2">Unit cm/s</param>
  218. /// <returns>Unit mmHg</returns>
  219. @override
  220. double countMaxPG(double v1, double v2) {
  221. // The velocity is in cm/s, but it should be m/s in this formula, so divide it by 100.
  222. double meanPG =
  223. 4.0 * (math.pow(v1 * 0.01, 2) - math.pow(v2 * 0.01, 2)).abs();
  224. return meanPG;
  225. }
  226. // 计算心率
  227. // TODO: 心脏周期(可配置) DefaultHeartCycle = 2;
  228. // Origin: Vinno.Modules.MeasureModule\Primitives\DopplerTraceBase.cs
  229. @override
  230. double countHR(double timeSpan) {
  231. const defaultHeartCycle = 1;
  232. double hr = (defaultHeartCycle * 60 / timeSpan).roundToDouble();
  233. return hr;
  234. }
  235. // return [VTI,tiEnv,timeAveragedVelocity,pv,meanPG,hr];
  236. @override
  237. List<double> countVTI(
  238. List<DPoint> tracePoints,
  239. ) {
  240. var tiEnv = 0.0;
  241. var timeAveragedVelocity = 0.0;
  242. var pv = 0.0;
  243. var meanPG = 0.0;
  244. if (tracePoints.length < 2) return [0, 0, 0, 0, 0, 0];
  245. int n = tracePoints.length - 1;
  246. double dis = 0;
  247. double startTime = 0;
  248. double endTime = 0;
  249. double pgSum = 0;
  250. int positiveCount = 0;
  251. int negativeCount = 0;
  252. for (int i = 0; i < n; i++) {
  253. var p1 = tracePoints[i];
  254. var p2 = tracePoints[i + 1];
  255. if (pv.abs() < (p1.y).abs()) {
  256. pv = p1.y;
  257. }
  258. double meanVelocity = (p1.y + p2.y).abs() / 2;
  259. double timeSpan = (p1.x - p2.x).abs();
  260. dis += meanVelocity * timeSpan;
  261. pgSum += countPressure(meanVelocity) * timeSpan;
  262. if (i == 0) {
  263. startTime = p1.x;
  264. endTime = p2.x;
  265. } else {
  266. startTime = math.min(startTime, p2.x);
  267. endTime = math.max(endTime, p2.x);
  268. }
  269. if (p1.y < 0) {
  270. negativeCount++;
  271. } else {
  272. positiveCount++;
  273. }
  274. }
  275. if (tracePoints[n].y < 0) {
  276. negativeCount++;
  277. } else {
  278. positiveCount++;
  279. }
  280. if (positiveCount < negativeCount) {
  281. dis = -dis;
  282. }
  283. if (pv.abs() < (tracePoints[n].y).abs()) {
  284. pv = tracePoints[n].y;
  285. }
  286. tiEnv = (endTime - startTime).abs();
  287. meanPG = (pgSum / tiEnv);
  288. timeAveragedVelocity = (dis / tiEnv);
  289. var hr = countHR(tiEnv);
  290. return [dis, tiEnv, timeAveragedVelocity, pv, meanPG, hr];
  291. }
  292. /// SV Diam
  293. ///
  294. /// SV = 1/4 x π x (Diam)^2 x VTI
  295. ///
  296. /// [diam] `cm`
  297. ///
  298. /// [vti] `cm`
  299. ///
  300. /// return `cm3`
  301. @override
  302. double svDiam(double diam, double vti) {
  303. double sv = math.pi * math.pow(diam, 2) * vti / 4;
  304. return sv;
  305. }
  306. /// CSA
  307. ///
  308. /// CSA = 1/4 x π x Diam^2
  309. ///
  310. /// [diam] `cm`
  311. ///
  312. /// return `cm²`
  313. @override
  314. double csa(double diam) {
  315. double csa = math.pi * math.pow(diam, 2) / 4;
  316. return csa;
  317. }
  318. /// CW/PW Mode: dp/dt = (4* V2 * V2 - 4 * V1 * V1)/ (T2 - T1)
  319. ///
  320. /// return `mmHg/s`
  321. @override
  322. double dpDtRatio(DPoint p1, DPoint p2) {
  323. double dp = maxPG(p2.y, p1.y);
  324. double dt = (p2.x - p1.x).abs();
  325. double result = dp / dt;
  326. return result;
  327. }
  328. /// 4.0 x |V1^2 - V2^2|
  329. ///
  330. /// [v1] `cm/s`
  331. ///
  332. /// [v2] `cm/s`
  333. ///
  334. /// reutrn `mmHg`
  335. @override
  336. double maxPG(double v1, double v2) {
  337. // The velocity is in cm/s, but it should be m/s in this formula, so divide it by 100.
  338. double meanPG =
  339. 4.0 * (math.pow(v1 * 0.01, 2) - math.pow(v2 * 0.01, 2)).abs();
  340. return meanPG;
  341. }
  342. /// 0.25 x π x D1 x D2
  343. ///
  344. /// [d1] `cm`
  345. ///
  346. /// [d2] `cm`
  347. ///
  348. /// return `cm²`
  349. @override
  350. double area(double d1, double d2) {
  351. double area = 0;
  352. if (!doubleAlmostEquals(d1, 0) && !doubleAlmostEquals(d2, 0)) {
  353. if ((d1 < 0.001 * d2) || (d2 < 0.001 * d1)) {
  354. // Stright line
  355. area = 0.0;
  356. } else {
  357. // Cirle
  358. area = 0.25 * d1 * d2 * math.pi;
  359. }
  360. }
  361. return area;
  362. }
  363. /// 0.25 x π x Diam^2 x TAMAX x Compensation Coefficient, where 0.5 ≤ Compensation Coefficient ≤ 1.0
  364. ///
  365. /// [flowArea] `cm²`
  366. ///
  367. /// [tamean] `cm/s`
  368. ///
  369. /// [coefficient] `[0.5,1.0]`
  370. ///
  371. /// return `cm³/s`
  372. @override
  373. double flowVolTAMAX(double flowArea, double tamean, double coefficient) {
  374. if (coefficient < 0.5 || coefficient > 1.0) {
  375. return double.nan;
  376. }
  377. double flowVol = flowArea * tamean.abs() * coefficient;
  378. return flowVol;
  379. }
  380. }
  381. class AnimalsGeneralFormulas extends BaseGeneralFormulas {}