general.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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 perimeter(double d1, double d2) => _singleton.perimeter(d1, d2);
  43. static double flowVolTAMAX(
  44. double flowArea, double tamean, double coefficient) =>
  45. _singleton.flowVolTAMAX(flowArea, tamean, coefficient);
  46. static double pi(double ps, double mdEd, double tamax) =>
  47. _singleton.pi(ps, mdEd, tamax);
  48. static double medianVelocity(double ps, double ed) =>
  49. _singleton.medianVelocity(ps, ed);
  50. static double volume(double d) => _singleton.volume(d);
  51. }
  52. abstract class IGeneralFormulaStrategy {
  53. double volumeTwoLine(double d1, double d2, [bool useBigValue = true]);
  54. double volumeWithCoefficient(
  55. double d1,
  56. double d2,
  57. double d3,
  58. double coefficient,
  59. );
  60. double countStenosis(double d1, double d2);
  61. bool doubleAlmostEquals(double num1, double num2,
  62. [double precision = 0.000001]);
  63. double distance2Line(DPoint pt1, DPoint pt2, DPoint pt);
  64. double countSlope(DPoint p1, DPoint p2);
  65. double countEnvelopeTime(List<DPoint> points);
  66. double countPressure(double v);
  67. double countRI(double ps, double mdEd);
  68. double countMaxPG(double v1, double v2);
  69. double countHR(double timeSpan);
  70. List<double> countVTI(
  71. List<DPoint> tracePoints,
  72. );
  73. double svDiam(double diam, double vti);
  74. double csa(double diam);
  75. double dpDtRatio(DPoint p1, DPoint p2);
  76. double maxPG(double v1, double v2);
  77. double area(double d1, double d2);
  78. double flowVolTAMAX(double flowArea, double tamean, double coefficient);
  79. double perimeter(double d1, double d2);
  80. double volume(double d);
  81. /// <summary>
  82. /// Pulastility Index
  83. /// <para>PI (ED):(PS - ED)/TAMAX</para>
  84. /// <para>PI (MD):(PS - MD)/TAMAX</para>
  85. /// </summary>
  86. /// <param name="ps">Unit cm/s</param>
  87. /// <param name="md_ed">Unit cm/s</param>
  88. /// <param name="tamax">Unit cm/s</param>
  89. /// <returns>Unit none</returns>
  90. /// 在TCCD下用Vmean替换tamax计算
  91. double pi(double ps, double mdEd, double tamax);
  92. /// <param name="ps">Unit cm/s</param>
  93. /// <param name="ed">Unit cm/s</param>
  94. /// <returns>Unit cm/s</returns>
  95. double medianVelocity(double ps, double ed);
  96. }
  97. class BaseGeneralFormulas implements IGeneralFormulaStrategy {
  98. /// Volume:1/6 x π
  99. static const double VolumeCofficient = math.pi / 6.0;
  100. /// 1/6 x π x D1^2 x D2 (D1 > D2)
  101. ///
  102. /// 1/6 x π x D1 x D2^2 (D1 ≤ D2)
  103. ///
  104. /// return unit: cm³</returns>
  105. @override
  106. double volumeTwoLine(double d1, double d2, [bool useBigValue = true]) {
  107. double volume = 0;
  108. if (!doubleAlmostEquals(d1, 0) && !doubleAlmostEquals(d2, 0)) {
  109. if (useBigValue) {
  110. if (d1 > d2) {
  111. volume = d1 * d1 * d2 * math.pi / 6.0;
  112. } else {
  113. volume = d1 * d2 * d2 * math.pi / 6.0;
  114. }
  115. } else {
  116. if (d1 > d2) {
  117. volume = d2 * d2 * d1 * math.pi / 6.0;
  118. } else {
  119. volume = d1 * d1 * d2 * math.pi / 6.0;
  120. }
  121. }
  122. }
  123. return volume;
  124. }
  125. /// 带比率系数算体积
  126. ///
  127. /// [coefficient] 比率系数
  128. @override
  129. double volumeWithCoefficient(
  130. double d1,
  131. double d2,
  132. double d3,
  133. double coefficient,
  134. ) {
  135. double volume = 0;
  136. if (!doubleAlmostEquals(d1, 0) &&
  137. !doubleAlmostEquals(d2, 0) &&
  138. !doubleAlmostEquals(d3, 0)) {
  139. volume = d1 * d2 * d3 * coefficient;
  140. }
  141. return volume;
  142. }
  143. /// 狭窄率计算
  144. @override
  145. double countStenosis(double d1, double d2) {
  146. double residual;
  147. double lumen;
  148. if (d1 > d2) {
  149. residual = d2;
  150. lumen = d1;
  151. } else {
  152. residual = d1;
  153. lumen = d2;
  154. }
  155. return (1.0 - residual / lumen) * 100;
  156. }
  157. @override
  158. bool doubleAlmostEquals(double num1, double num2,
  159. [double precision = 0.000001]) {
  160. if (num1.isNaN && num2.isNaN) return true;
  161. return (num1 - num2).abs() <= precision;
  162. }
  163. /// 点到线的距离
  164. ///
  165. /// [pt1] 线端点1
  166. ///
  167. /// [pt2] 线端点2
  168. ///
  169. /// [pt] 点
  170. @override
  171. double distance2Line(DPoint pt1, DPoint pt2, DPoint pt) {
  172. double dis = 0;
  173. if (pt1.x == pt2.x) {
  174. dis = (pt.x - pt1.x).abs();
  175. return dis;
  176. }
  177. var lineK = (pt2.y - pt1.y) / (pt2.x - pt1.x);
  178. var lineC = (pt2.x * pt1.y - pt1.x * pt2.y) / (pt2.x - pt1.x);
  179. dis = (lineK * pt.x - pt.y + lineC).abs() / (math.sqrt(lineK * lineK + 1));
  180. return dis;
  181. }
  182. ///计算斜率
  183. @override
  184. double countSlope(DPoint p1, DPoint p2) {
  185. if (doubleAlmostEquals(p2.x, p1.x)) return 0;
  186. double vertical = p2.y - p1.y;
  187. double time = (p2.x - p1.x).abs();
  188. double slope = vertical / time;
  189. return slope;
  190. }
  191. ///计算包络时间
  192. ///Origin: Vinno.Modules.MeasureModule\Formulas\GeneralFormulas.cs [632:67]
  193. @override
  194. double countEnvelopeTime(List<DPoint> points) {
  195. if (points.length < 2) return 0;
  196. double startTime = double.maxFinite;
  197. double endTime = -double.maxFinite;
  198. for (var point in points) {
  199. if (point.x < startTime) {
  200. startTime = point.x;
  201. }
  202. if (point.x > endTime) {
  203. endTime = point.x;
  204. }
  205. }
  206. return (endTime - startTime).abs();
  207. }
  208. /// <summary>
  209. /// 4.0 x V^2
  210. /// </summary>
  211. /// <param name="v">Unit cm/s</param>
  212. /// <returns>Unit mmHg</returns>
  213. @override
  214. double countPressure(double v) {
  215. // The velocity is in cm/s, but it should be m/s in this formula, so divide it by 100.
  216. double pg = 4.0 * math.pow(v * 0.01, 2);
  217. return pg;
  218. }
  219. /// <summary>
  220. /// Resistivity Index 阻力指数
  221. /// <para>RI (ED)=(PS - ED)/PS </para>
  222. /// <para>RI (MD)=(PS - MD)/PS </para>
  223. /// </summary>
  224. /// <param name="ps">Unit cm/s</param>
  225. /// <param name="mdEd">Unit cm/s</param>
  226. @override
  227. double countRI(double ps, double mdEd) {
  228. ps = ps.abs();
  229. mdEd = mdEd.abs();
  230. double ri = (ps - mdEd) / ps;
  231. if (ri < 0) {
  232. return double.nan;
  233. }
  234. return ri;
  235. }
  236. /// <summary>
  237. /// 4.0 x |V1^2 - V2^2|
  238. /// </summary>
  239. /// <param name="v1">Unit cm/s</param>
  240. /// <param name="v2">Unit cm/s</param>
  241. /// <returns>Unit mmHg</returns>
  242. @override
  243. double countMaxPG(double v1, double v2) {
  244. // The velocity is in cm/s, but it should be m/s in this formula, so divide it by 100.
  245. double meanPG =
  246. 4.0 * (math.pow(v1 * 0.01, 2) - math.pow(v2 * 0.01, 2)).abs();
  247. return meanPG;
  248. }
  249. // 计算心率
  250. // TODO: 心脏周期(可配置) DefaultHeartCycle = 2;
  251. // Origin: Vinno.Modules.MeasureModule\Primitives\DopplerTraceBase.cs
  252. @override
  253. double countHR(double timeSpan) {
  254. const defaultHeartCycle = 1;
  255. double hr = (defaultHeartCycle * 60 / timeSpan).roundToDouble();
  256. return hr;
  257. }
  258. // return [VTI,tiEnv,timeAveragedVelocity,pv,meanPG,hr];
  259. @override
  260. List<double> countVTI(
  261. List<DPoint> tracePoints,
  262. ) {
  263. var tiEnv = 0.0;
  264. var timeAveragedVelocity = 0.0;
  265. var pv = 0.0;
  266. var meanPG = 0.0;
  267. if (tracePoints.length < 2) return [0, 0, 0, 0, 0, 0];
  268. int n = tracePoints.length - 1;
  269. double dis = 0;
  270. double startTime = 0;
  271. double endTime = 0;
  272. double pgSum = 0;
  273. int positiveCount = 0;
  274. int negativeCount = 0;
  275. for (int i = 0; i < n; i++) {
  276. var p1 = tracePoints[i];
  277. var p2 = tracePoints[i + 1];
  278. if (pv.abs() < (p1.y).abs()) {
  279. pv = p1.y;
  280. }
  281. double meanVelocity = (p1.y + p2.y).abs() / 2;
  282. double timeSpan = (p1.x - p2.x).abs();
  283. dis += meanVelocity * timeSpan;
  284. pgSum += countPressure(meanVelocity) * timeSpan;
  285. if (i == 0) {
  286. startTime = p1.x;
  287. endTime = p2.x;
  288. } else {
  289. startTime = math.min(startTime, p2.x);
  290. endTime = math.max(endTime, p2.x);
  291. }
  292. if (p1.y < 0) {
  293. negativeCount++;
  294. } else {
  295. positiveCount++;
  296. }
  297. }
  298. if (tracePoints[n].y < 0) {
  299. negativeCount++;
  300. } else {
  301. positiveCount++;
  302. }
  303. if (positiveCount < negativeCount) {
  304. dis = -dis;
  305. }
  306. if (pv.abs() < (tracePoints[n].y).abs()) {
  307. pv = tracePoints[n].y;
  308. }
  309. tiEnv = (endTime - startTime).abs();
  310. meanPG = (pgSum / tiEnv);
  311. timeAveragedVelocity = (dis / tiEnv);
  312. var hr = countHR(tiEnv);
  313. return [dis, tiEnv, timeAveragedVelocity, pv, meanPG, hr];
  314. }
  315. /// SV Diam
  316. ///
  317. /// SV = 1/4 x π x (Diam)^2 x VTI
  318. ///
  319. /// [diam] `cm`
  320. ///
  321. /// [vti] `cm`
  322. ///
  323. /// return `cm3`
  324. @override
  325. double svDiam(double diam, double vti) {
  326. double sv = math.pi * math.pow(diam, 2) * vti / 4;
  327. return sv;
  328. }
  329. /// CSA
  330. ///
  331. /// CSA = 1/4 x π x Diam^2
  332. ///
  333. /// [diam] `cm`
  334. ///
  335. /// return `cm²`
  336. @override
  337. double csa(double diam) {
  338. double csa = math.pi * math.pow(diam, 2) / 4;
  339. return csa;
  340. }
  341. /// CW/PW Mode: dp/dt = (4* V2 * V2 - 4 * V1 * V1)/ (T2 - T1)
  342. ///
  343. /// return `mmHg/s`
  344. @override
  345. double dpDtRatio(DPoint p1, DPoint p2) {
  346. double dp = maxPG(p2.y, p1.y);
  347. double dt = (p2.x - p1.x).abs();
  348. double result = dp / dt;
  349. return result;
  350. }
  351. /// 4.0 x |V1^2 - V2^2|
  352. ///
  353. /// [v1] `cm/s`
  354. ///
  355. /// [v2] `cm/s`
  356. ///
  357. /// reutrn `mmHg`
  358. @override
  359. double maxPG(double v1, double v2) {
  360. // The velocity is in cm/s, but it should be m/s in this formula, so divide it by 100.
  361. double meanPG =
  362. 4.0 * (math.pow(v1 * 0.01, 2) - math.pow(v2 * 0.01, 2)).abs();
  363. return meanPG;
  364. }
  365. /// 0.25 x π x D1 x D2
  366. ///
  367. /// [d1] `cm`
  368. ///
  369. /// [d2] `cm`
  370. ///
  371. /// return `cm²`
  372. @override
  373. double area(double d1, double d2) {
  374. double area = 0;
  375. if (!doubleAlmostEquals(d1, 0) && !doubleAlmostEquals(d2, 0)) {
  376. if ((d1 < 0.001 * d2) || (d2 < 0.001 * d1)) {
  377. // Stright line
  378. area = 0.0;
  379. } else {
  380. // Cirle
  381. area = 0.25 * d1 * d2 * math.pi;
  382. }
  383. }
  384. return area;
  385. }
  386. /// 0.25 x π x Diam^2 x TAMAX x Compensation Coefficient, where 0.5 ≤ Compensation Coefficient ≤ 1.0
  387. ///
  388. /// [flowArea] `cm²`
  389. ///
  390. /// [tamean] `cm/s`
  391. ///
  392. /// [coefficient] `[0.5,1.0]`
  393. ///
  394. /// return `cm³/s`
  395. @override
  396. double flowVolTAMAX(double flowArea, double tamean, double coefficient) {
  397. if (coefficient < 0.5 || coefficient > 1.0) {
  398. return double.nan;
  399. }
  400. double flowVol = flowArea * tamean.abs() * coefficient;
  401. return flowVol;
  402. }
  403. @override
  404. double pi(double ps, double mdEd, double tamax) {
  405. double pi = (ps - mdEd) / tamax;
  406. return pi.abs();
  407. }
  408. @override
  409. double medianVelocity(double ps, double ed) {
  410. ps = ps.abs();
  411. ed = ed.abs();
  412. return (ps + 2 * ed) / 3;
  413. }
  414. @override
  415. double perimeter(double d1, double d2) {
  416. double perimeter = 0.0;
  417. if (!almostEquals(d1, 0.0) && !almostEquals(d2, 0.0)) {
  418. if (almostEquals(d1, d2)) {
  419. // Circle
  420. perimeter = d1 * math.pi;
  421. } else if (d1 < 0.001 * d2) {
  422. // Straight line
  423. perimeter = 2.0 * d2;
  424. } else if (d2 < 0.001 * d1) {
  425. // Straight line
  426. perimeter = 2.0 * d1;
  427. } else {
  428. // Ellipse
  429. double a = d1 > d2 ? d1 / 2.0 : d2 / 2.0; // longest half axis
  430. double b = d1 < d2 ? d1 / 2.0 : d2 / 2.0; // shortest half axis
  431. if (a < 0.0001) {
  432. // Long axis too short to make computations. Return zero.
  433. perimeter = 0.0;
  434. } else {
  435. // Calculate k, kc
  436. double k = math.sqrt(a * a - b * b) / a;
  437. double kc2 = 1 - k * k;
  438. perimeter = 4.0 * a * ellipseCircum(math.sqrt(kc2), kc2);
  439. }
  440. }
  441. }
  442. return perimeter;
  443. }
  444. bool almostEquals(double a, double b, [double epsilon = 1e-10]) {
  445. return (a - b).abs() < epsilon;
  446. }
  447. double ellipseCircum(double k, double kc2) {
  448. // Placeholder for the EllipseCircum method equivalent.
  449. // You can implement the complete elliptic integral of the second kind here
  450. // or use an approximation method for ellipse circumference.
  451. // For the sake of this example, we will use Ramanujan's approximation.
  452. double h = math.pow((k - 1), 2) / math.pow((k + 1), 2);
  453. return math.pi * (1 + 3 * h / (10 + math.sqrt(4 - 3 * h)));
  454. }
  455. @override
  456. double volume(double d) {
  457. double volume = 0.0;
  458. if (!almostEquals(d, 0.0)) {
  459. volume = math.pow(d, 3) * math.pi / 6.0;
  460. }
  461. return volume;
  462. }
  463. }
  464. class AnimalsGeneralFormulas extends BaseGeneralFormulas {}