ReportInterface.class.php 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. <?php
  2. namespace Models {
  3. require_once 'Models/User.class.php';
  4. require_once 'Tools/Random.class.php';
  5. require_once 'Tools/FS.class.php';
  6. require_once 'Tools/UUID.class.php';
  7. require_once 'External/dompdf_0-8-6/autoload.inc.php';
  8. require_once 'External/ImageWorkshop/vendor/autoload.php';
  9. require_once 'External/ImageWorkshop/src/ImageWorkshop.php';
  10. use PHPImageWorkshop\ImageWorkshop;
  11. class ReportInterface {
  12. //
  13. protected $DataInterface;
  14. /**
  15. *
  16. */
  17. public function __construct($DataInterface) {
  18. $this->DataInterface = $DataInterface;
  19. }
  20. /**
  21. * Get media
  22. */
  23. public function reportGet($User, $patientID, $visitID) {
  24. $userID = $User->ID;
  25. // patient
  26. $statement = $this->DataInterface->DatabaseConnection->prepare(
  27. "SELECT patient.* FROM patient WHERE ID = :fk_patient"
  28. );
  29. $statement->bindParam(':fk_patient', $patientID);
  30. // Error check
  31. if(!$statement->execute()) {
  32. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  33. }
  34. $patient = $statement->fetchAll(\PDO::FETCH_ASSOC)[0];
  35. // visit
  36. $statement = $this->DataInterface->DatabaseConnection->prepare(
  37. "SELECT area, markers FROM visit WHERE ID = :fk_visit"
  38. );
  39. $statement->bindParam(':fk_visit', $visitID);
  40. // Error check
  41. if(!$statement->execute()) {
  42. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  43. }
  44. $data = $statement->fetchAll(\PDO::FETCH_ASSOC)[0];
  45. // media
  46. $statement = $this->DataInterface->DatabaseConnection->prepare(
  47. "SELECT * FROM media WHERE fk_visit = :fk_visit"
  48. );
  49. $statement->bindParam(':fk_visit', $visitID);
  50. // Error check
  51. if(!$statement->execute()) {
  52. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  53. }
  54. $media = $statement->fetchAll(\PDO::FETCH_ASSOC);
  55. foreach($media as &$m) {
  56. $m['metrics'] = json_decode($m['metrics']);
  57. // measures
  58. $statement = $this->DataInterface->DatabaseConnection->prepare(
  59. "SELECT * FROM measure WHERE fk_media = :fk_media"
  60. );
  61. $statement->bindParam(':fk_media', $m['ID']);
  62. // Error check
  63. if(!$statement->execute()) {
  64. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  65. }
  66. $m['measure'] = $statement->fetchAll(\PDO::FETCH_ASSOC);
  67. foreach($m['measure'] as &$measure) {
  68. $measure['points'] = json_decode($measure['points']);
  69. $measure['computation'] = json_decode($measure['computation']);
  70. }
  71. }
  72. // Abacus
  73. $statement = $this->DataInterface->DatabaseConnection->prepare(
  74. "SELECT DISTINCT name FROM abacus ORDER BY name"
  75. );
  76. // Error check
  77. if(!$statement->execute()) {
  78. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  79. }
  80. $abacus = $statement->fetchAll(\PDO::FETCH_ASSOC);
  81. // Recipient
  82. $statement = $this->DataInterface->DatabaseConnection->prepare(
  83. "SELECT * FROM recipient WHERE fk_user = :fk_user ORDER BY email"
  84. );
  85. $statement->bindParam(':fk_user', $userID);
  86. // Error check
  87. if(!$statement->execute()) {
  88. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  89. }
  90. $recipient = $statement->fetchAll(\PDO::FETCH_ASSOC);
  91. // PACS
  92. $statement = $this->DataInterface->DatabaseConnection->prepare(
  93. "SELECT data FROM settings_pacs WHERE fk_physician = $userID"
  94. );
  95. if(!$statement->execute()) {
  96. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  97. }
  98. $pacs = json_decode($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['data']);
  99. return [
  100. 'result' => 'OK',
  101. 'patient' => $patient,
  102. 'area' => $data['area'],
  103. 'markers' => json_decode($data['markers']),
  104. 'media' => $media,
  105. 'recipient' => $recipient,
  106. 'pacs' => $pacs,
  107. 'abacus' => $abacus
  108. ];
  109. }
  110. /**
  111. *
  112. */
  113. public function reportMailAddPost($User, $data) {
  114. $userID = $User->ID;
  115. $statement = $this->DataInterface->DatabaseConnection->prepare(
  116. "INSERT INTO recipient(ID, email, fk_user) VALUES(0, :email, :fk_user)"
  117. );
  118. $statement->bindParam(':email', $data['email']);
  119. $statement->bindParam(':fk_user', $userID);
  120. // Error check
  121. if(!$statement->execute()) {
  122. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  123. }
  124. $emailID = $this->DataInterface->DatabaseConnection->lastInsertId();
  125. return [
  126. 'result' => 'OK',
  127. 'ID' => $emailID
  128. ];
  129. }
  130. /**
  131. *
  132. */
  133. public function reportMailDeletePost($User, $data) {
  134. $userID = $User->ID;
  135. $statement = $this->DataInterface->DatabaseConnection->prepare(
  136. "DELETE FROM recipient WHERE ID = :ID"
  137. );
  138. $statement->bindParam(':ID', $data['ID']);
  139. // Error check
  140. if(!$statement->execute()) {
  141. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  142. }
  143. return [
  144. 'result' => 'OK'
  145. ];
  146. }
  147. /**
  148. *
  149. */
  150. protected function generateMeasureText($lang, &$measureList, $M, &$imt_value, &$imt_count) {
  151. if($M['type']=='imt') {
  152. $measureList[] =
  153. ($this->tr($lang, 'imt_avg').': '.number_format($M['computation']->imt_mean, 3)).' mm, '.
  154. ($this->tr($lang, 'imt_max').': '.number_format($M['computation']->imt_max, 3)).' mm';
  155. $imt_value+=$M['computation']->imt_mean;
  156. $imt_count++;
  157. }
  158. else if($M['type']=='plaque') {
  159. $measureList[] =
  160. ($this->tr($lang, 'plaque').': '.round($M['computation']->plaque_area*100)/100).' mm², '.
  161. ($this->tr($lang, 'thk_max').': '.round($M['computation']->plaque_max_thickness*10)/10).' mm';
  162. }
  163. else if($M['type']=='area') {
  164. $measureList[] = ($this->tr($lang, 'surface').': '.round($M['computation']->surface*100)/100).' mm²';
  165. }
  166. else if($M['type']=='thickness') {
  167. $measureList[] = ($this->tr($lang, 'thk').': '.round($M['computation']->distance*10)/10).' mm';
  168. }
  169. else if($M['type']=='plaque_thickness') {
  170. $measureList[] = ($this->tr($lang, 'thk_plaque').': '.round($M['computation']->distance*10)/10).' mm';
  171. }
  172. else if($M['type']=='diameter') {
  173. $measureList[] = ($this->tr($lang, 'diameter').': '.round($M['computation']->distance*10)/10).' mm';
  174. }
  175. }
  176. /**
  177. *
  178. */
  179. protected function generateImageCell($lang, $side, $visit, $visitID, $markers, $media, $ID, &$imt_value, &$imt_count, $mode) {
  180. $M = null;
  181. for($i=0; $i<count($media); $i++) {
  182. if($media[$i]['ID'] == $ID) {
  183. $M = $media[$i];
  184. break;
  185. }
  186. }
  187. $span = count($media)==2?3:4;
  188. if(!$M) {
  189. return '<tr><td colspan="'.$span.'" style="height: 16px;">not found</td></tr>';
  190. }
  191. // segment
  192. $incidence = array_key_exists('incidence', $M)?(' ('.$this->tr($lang, $M['incidence']).')'):'';
  193. $segmentName = $this->tr($lang, $visit['area'].'_'.$side.'_'.$M['location']).' '.$incidence;
  194. $segmentImage = '../../storage/area/area_'.$visit['area'].'_'.($side=='right'?'R':'L').'_'.$M['location'].'.png';
  195. $segmentLayer = ImageWorkshop::initFromPath($segmentImage);
  196. for($mk=0; $mk<count($visit['markers']); $mk++) {
  197. if($visit['markers'][$mk]->location==$side) {
  198. $segmentLayer->addLayerOnTop($markers[$visit['markers'][$mk]->type], $visit['markers'][$mk]->x, $visit['markers'][$mk]->y);
  199. }
  200. }
  201. ob_start();
  202. imagejpeg($segmentLayer->getResult(), null, 100);
  203. $segmentBase64 = ob_get_contents();
  204. ob_end_clean();
  205. $segmentBase64 = 'data:image/jpeg;base64,'.base64_encode($segmentBase64);
  206. // image
  207. $frame = 0;
  208. for($measure=0; $measure<count($M['measure']); $measure++) {
  209. $mm = $M['measure'][$measure];
  210. if($mm['type'] != 'calibration') {
  211. $frame = $mm['frame'];
  212. break;
  213. }
  214. }
  215. $path = '../../storage/media/'.$visitID.'/'.$M['filename'];
  216. $path_parts = pathinfo($path);
  217. if(property_exists($M['metrics'], 'fps')) {
  218. $path = $path_parts['dirname'].'/'.$path_parts['filename'].'-'.$frame.'.jpeg';
  219. }
  220. else {
  221. $path = $path_parts['dirname'].'/'.$path_parts['filename'].'.jpeg';
  222. }
  223. // draw measures
  224. $mImage = imagecreatefromjpeg($path);
  225. $orange = imagecolorallocate($mImage, 255,165,0);
  226. $green = imagecolorallocate($mImage, 0, 255, 0);
  227. imagesetthickness($mImage, 2);
  228. foreach($M['measure'] as $Mm) {
  229. if($Mm['type']=='imt') {
  230. for($p=0; $p<count($Mm['computation']->vect_adventitia); $p++) {
  231. imageline($mImage, $Mm['computation']->vect_adventitia[$p]->x, $Mm['computation']->vect_adventitia[$p]->y, $Mm['computation']->vect_adventitia[$p]->x+1, $Mm['computation']->vect_adventitia[$p]->y, $green);
  232. }
  233. for($p=0; $p<count($Mm['computation']->vect_intima); $p++) {
  234. imageline($mImage, $Mm['computation']->vect_intima[$p]->x, $Mm['computation']->vect_intima[$p]->y, $Mm['computation']->vect_intima[$p]->x+1, $Mm['computation']->vect_intima[$p]->y, $orange);
  235. }
  236. }
  237. else if($Mm['type']=='plaque') {
  238. for($p=0; $p<count($Mm['computation']->longeantPlaque)-1; $p++) {
  239. imageline($mImage, $Mm['computation']->longeantPlaque[$p]->x, $Mm['computation']->longeantPlaque[$p]->y, $Mm['computation']->longeantPlaque[$p+1]->x, $Mm['computation']->longeantPlaque[$p+1]->y, $orange);
  240. }
  241. for($p=0; $p<count($Mm['computation']->ptsTrouvesFin)-1; $p++) {
  242. imageline($mImage, $Mm['computation']->ptsTrouvesFin[$p]->x, $Mm['computation']->ptsTrouvesFin[$p]->y, $Mm['computation']->ptsTrouvesFin[$p+1]->x, $Mm['computation']->ptsTrouvesFin[$p+1]->y, $green);
  243. }
  244. imageline($mImage, $Mm['computation']->longeantPlaque[0]->x, $Mm['computation']->longeantPlaque[0]->y, $Mm['computation']->ptsTrouvesFin[0]->x, $Mm['computation']->ptsTrouvesFin[0]->y, $green);
  245. imageline($mImage, $Mm['computation']->longeantPlaque[count($Mm['computation']->longeantPlaque)-1]->x, $Mm['computation']->longeantPlaque[count($Mm['computation']->longeantPlaque)-1]->y, $Mm['computation']->ptsTrouvesFin[count($Mm['computation']->ptsTrouvesFin)-1]->x, $Mm['computation']->ptsTrouvesFin[count($Mm['computation']->ptsTrouvesFin)-1]->y, $green);
  246. }
  247. else if($Mm['type']=='area') {
  248. for($p=0; $p<count($Mm['points'])-1; $p++) {
  249. imageline($mImage, $Mm['points'][$p]->x, $Mm['points'][$p]->y, $Mm['points'][$p+1]->x, $Mm['points'][$p+1]->y, $green);
  250. }
  251. imageline($mImage, $Mm['points'][0]->x, $Mm['points'][0]->y, $Mm['points'][count($Mm['points'])-1]->x, $Mm['points'][count($Mm['points'])-1]->y, $green);
  252. for($p=0; $p<count($Mm['points'])-1; $p++) {
  253. imagefilledellipse($mImage, $Mm['points'][$p]->x, $Mm['points'][$p]->y, 10, 10, $orange);
  254. }
  255. }
  256. else if($Mm['type']=='diameter' || $Mm['type']=='thickness' || $Mm['type']=='plaque_thickness') {
  257. imageline($mImage, $Mm['points'][0]->x, $Mm['points'][0]->y, $Mm['points'][1]->x, $Mm['points'][1]->y, $green);
  258. imageline($mImage, $Mm['points'][0]->x, $Mm['points'][0]->y, $Mm['points'][1]->x, $Mm['points'][1]->y, $green);
  259. imagefilledellipse($mImage, $Mm['points'][0]->x, $Mm['points'][0]->y, 10, 10, $orange);
  260. imagefilledellipse($mImage, $Mm['points'][1]->x, $Mm['points'][1]->y, 10, 10, $orange);
  261. }
  262. }
  263. ob_start();
  264. imagejpeg($mImage);
  265. $imageData = ob_get_contents();
  266. ob_end_clean();
  267. $imageBase64 = 'data:image/jpeg;base64,'.base64_encode($imageData);
  268. imagedestroy($mImage);
  269. //$imageBase64 = 'data:image/' . pathinfo(parse_url($path, PHP_URL_PATH), PATHINFO_EXTENSION) . ';base64,' . base64_encode(file_get_contents($path));
  270. // measures
  271. $measureList = [];
  272. foreach($M['measure'] as $Mm) {
  273. if($frame==intval($Mm['frame']) && $Mm['type']!='calibration') {
  274. $this->generateMeasureText($lang, $measureList, $Mm, $imt_value, $imt_count);
  275. }
  276. }
  277. // layout
  278. if($mode == '2R') {
  279. return '<tr><td colspan="'.$span.'" style="height: 16px;"></td></tr>'.
  280. '<tr>'.
  281. '<td style="width: 20%" valign="top"><img src="'.$segmentBase64.'" style="width: 100%;" /></td>'.
  282. '<td style="width: 60%" align="center">'.
  283. '<img src="'.$imageBase64.'" style="width: 90%;" /><div style="width: 100%; text-align: center;"><b>'.$segmentName.'</b><br/>'.implode('<br/>',$measureList).'</div>'.
  284. '</td>'.
  285. '<td style="width: 20%; text-align: center;"><h3>'.$this->tr($lang, $side).'</h3></td>'.
  286. '</tr>';
  287. }
  288. if($mode == '2L') {
  289. return '<tr><td colspan="'.$span.'" style="height: 16px;"></td></tr>'.
  290. '<tr>'.
  291. '<td style="width: 20%; text-align: center;"><h3>'.$this->tr($lang, $side).'</h3></td>'.
  292. '<td style="width: 60%" align="center">'.
  293. '<img src="'.$imageBase64.'" style="width: 90%;" /><div style="width: 100%; text-align: center;"><b>'.$segmentName.'</b><br/>'.implode('<br/>',$measureList).'</div>'.
  294. '</td>'.
  295. '<td style="width: 20%" valign="top"><img src="'.$segmentBase64.'" style="width: 100%;" /></td>'.
  296. '</tr>';
  297. }
  298. if($mode == '6R') {
  299. return '<td style="width: 15%" valign="top"><img src="'.$segmentBase64.'" style="width: 100%;" /></td>'.
  300. '<td style="width: 35%" valign="top">'.
  301. '<img src="'.$imageBase64.'" style="width: 100%;" /><div style="width: 100%; text-align: center;"><b>'.$segmentName.'</b><br/>'.implode('<br/>',$measureList).'</div>'.
  302. '</td>';
  303. }
  304. if($mode == '6L') {
  305. return '<td style="width: 35%" valign="top">'.
  306. '<img src="'.$imageBase64.'" style="width: 100%;" /><div style="width: 100%; text-align: center;"><b>'.$segmentName.'</b><br/>'.implode('<br/>',$measureList).'</div>'.
  307. '</td>'.
  308. '<td style="width: 15%" valign="top"><img src="'.$segmentBase64.'" style="width: 100%;" /></td>';
  309. }
  310. }
  311. /**
  312. *
  313. */
  314. public function reportPdfMailPost($User, $data) {
  315. $userID = $User->ID;
  316. // report
  317. $res = $this->reportPdfDownloadPost($User, $data);
  318. if($res['result']=='ERROR') {
  319. return $res;
  320. }
  321. foreach($data['emails'] as $email) {
  322. $res = $this->DataInterface->sendMail($User->email, $email, $data['subject'], $data['message'], '../../storage/report/'.$res['filename']);
  323. if($res['result']=='ERROR') {
  324. return $res;
  325. }
  326. }
  327. return [
  328. 'result' => 'OK',
  329. 'report' => $res
  330. ];
  331. }
  332. /**
  333. *
  334. */
  335. public function reportPdfPACSPost($User, $data) {
  336. $userID = $User->ID;
  337. // report
  338. $res = $this->reportPdfDownloadPost($User, $data);
  339. if($res['result']=='ERROR') {
  340. return $res;
  341. }
  342. $reportPath = '../../storage/report/'.$res['filename'];
  343. $dicomPath = $reportPath.'.dcm';
  344. // PACS
  345. $statement = $this->DataInterface->DatabaseConnection->prepare(
  346. "SELECT data FROM settings_pacs WHERE fk_physician = $userID"
  347. );
  348. if(!$statement->execute()) {
  349. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  350. }
  351. $pacs = json_decode($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['data']);
  352. // convert to dicom
  353. // if comming from a DICOM study, then use existing ID/UID from with this:
  354. // ' +st '.$aDicomFile.
  355. $patientName = $res['patient']['lastname'].'^'.$res['patient']['firstname'];
  356. $patientID = $res['patient']['patientID'];
  357. $patientBirth = str_replace('-','',$res['patient']['birthDate']);
  358. $patientSex = $res['patient']['gender'];
  359. $studyDate = str_replace('-','',$res['visit']['visitDate']);
  360. $cmdLine = 'pdf2dcm '.$reportPath.' '.$dicomPath.
  361. ' +pn "'.$patientName.'" +pi "'.$patientID.'" +pb '.$patientBirth.' +ps '.$patientSex.
  362. ' -k StudyDate="'.$studyDate.'" '.
  363. ' -v 2>&1';
  364. $output=null;
  365. $retval=null;
  366. exec($cmdLine, $output, $retval);
  367. if($retval !== 0 || count($output)<1) {
  368. return [
  369. 'result' => 'ERROR',
  370. 'cmdLine' => $cmdLine,
  371. 'output' => $output,
  372. 'retval' => $retval
  373. ];
  374. }
  375. // send
  376. $cmdLine = 'storescu '.$pacs->serverAddress.' '.$pacs->queryPort.' '.$dicomPath.' -aet '.$pacs->callingAET.' -aec '.$pacs->calledAET.
  377. ' -v 2>&1';
  378. $output=null;
  379. $retval=null;
  380. exec($cmdLine, $output, $retval);
  381. if($retval !== 0 || count($output)<1) {
  382. return [
  383. 'result' => 'ERROR',
  384. 'cmdLine' => $cmdLine,
  385. 'output' => $output,
  386. 'retval' => $retval
  387. ];
  388. }
  389. unlink($dicomPath);
  390. return [
  391. 'result' => 'OK',
  392. 'report' => $res,
  393. 'cmdLine' => $cmdLine
  394. ];
  395. }
  396. /**
  397. *
  398. */
  399. public function reportPdfDownloadPost($User, $data) {
  400. $userID = $User->ID;
  401. $visitID = $data['visitID'];
  402. $lang = $data['lang'];
  403. $reportType = $data['type'];
  404. $mediaList = $data['mediaList'];
  405. // style
  406. $style = '.page-break { page-break-after: always; } '.
  407. 'h1 { width: 100%; margin: 0 0 16px 0; padding: 0; text-align: center; } '.
  408. 'h3 { width: 100%; margin: 0 0 16px 0; padding: 0; text-align: center; } '.
  409. '.footer { position: fixed; bottom: 0; width: 100%; text-align: center; }'.
  410. '.blue { color: rgb(0,0,255); }'.
  411. '.grid { border-collapse: collapse; } .grid td { border: 1px solid black; text-align: center; }';
  412. // user
  413. $statement = $this->DataInterface->DatabaseConnection->prepare(
  414. "SELECT * FROM user, organization WHERE organization.fk_user = user.ID AND user.ID = $userID"
  415. );
  416. if(!$statement->execute()) {
  417. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  418. }
  419. $user = $statement->fetchAll(\PDO::FETCH_ASSOC)[0];
  420. // patient
  421. $statement = $this->DataInterface->DatabaseConnection->prepare(
  422. "SELECT * FROM patient, visit WHERE visit.fk_patient = patient.ID AND visit.ID = $visitID"
  423. );
  424. if(!$statement->execute()) {
  425. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  426. }
  427. $patient = $statement->fetchAll(\PDO::FETCH_ASSOC)[0];
  428. // visit
  429. $statement = $this->DataInterface->DatabaseConnection->prepare(
  430. "SELECT area, markers, visitDate FROM visit WHERE ID = :fk_visit"
  431. );
  432. $statement->bindParam(':fk_visit', $visitID);
  433. if(!$statement->execute()) {
  434. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  435. }
  436. $visit = $statement->fetchAll(\PDO::FETCH_ASSOC)[0];
  437. $visit['markers'] = json_decode($visit['markers']);
  438. // media
  439. $str = implode(',',$mediaList);
  440. $statement = $this->DataInterface->DatabaseConnection->prepare(
  441. "SELECT ID, side, location, filename, metrics FROM media WHERE ID IN ($str)"
  442. );
  443. if(!$statement->execute()) {
  444. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  445. }
  446. $media = $statement->fetchAll(\PDO::FETCH_ASSOC);
  447. foreach($media as &$m) {
  448. $m['metrics'] = json_decode($m['metrics']);
  449. // measures
  450. $statement = $this->DataInterface->DatabaseConnection->prepare(
  451. "SELECT * FROM measure WHERE fk_media = :fk_media ORDER BY ID"
  452. );
  453. $statement->bindParam(':fk_media', $m['ID']);
  454. if(!$statement->execute()) {
  455. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  456. }
  457. $m['measure'] = $statement->fetchAll(\PDO::FETCH_ASSOC);
  458. foreach($m['measure'] as &$_measure) {
  459. $_measure['points'] = json_decode($_measure['points']);
  460. $_measure['computation'] = json_decode($_measure['computation']);
  461. }
  462. }
  463. // organization
  464. $statement = $this->DataInterface->DatabaseConnection->prepare(
  465. "SELECT * FROM organization WHERE fk_user = $userID"
  466. );
  467. if(!$statement->execute()) {
  468. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  469. }
  470. $organization = $statement->fetchAll(\PDO::FETCH_ASSOC)[0];
  471. $org_name = $organization['name'];
  472. $org_address = $organization['address'].' '.$organization['zip'].' '.$organization['city'].' - '.$organization['phone'];
  473. // markers
  474. $markers = [
  475. "plaque" => ImageWorkshop::initFromPath('../../storage/marker/plaque.png'),
  476. "stenose" => ImageWorkshop::initFromPath('../../storage/marker/stenose.png'),
  477. "occlusion" => ImageWorkshop::initFromPath('../../storage/marker/occlusion.png')
  478. ];
  479. // IMT
  480. $imt_value_right = 0;
  481. $imt_count_right = 0;
  482. $imt_value_left = 0;
  483. $imt_count_left = 0;
  484. // 1st page
  485. $pics = '<table style="width: 100%;" border="0">';
  486. if($reportType==2) {
  487. $pics .= $this->generateImageCell($lang, 'right', $visit, $visitID, $markers, $media, $mediaList[0], $imt_value_right, $imt_count_right, '2R');
  488. $pics .= $this->generateImageCell($lang, 'left', $visit, $visitID, $markers, $media, $mediaList[1], $imt_value_left, $imt_count_left, '2L');
  489. }
  490. else {
  491. $pics .= '<tr><td colspan="4" style="height: 16px;"></td></tr>'.'<tr>';
  492. $pics .= $this->generateImageCell($lang, 'right', $visit, $visitID, $markers, $media, $mediaList[0], $imt_value_right, $imt_count_right, '6R');
  493. $pics .= $this->generateImageCell($lang, 'left', $visit, $visitID, $markers, $media, $mediaList[3], $imt_value_left, $imt_count_left, '6L');
  494. $pics .= '</tr>';
  495. $pics .= '<tr><td colspan="4" style="height: 16px;"></td></tr>'.'<tr>';
  496. $pics .= $this->generateImageCell($lang, 'right', $visit, $visitID, $markers, $media, $mediaList[1], $imt_value_right, $imt_count_right, '6R');
  497. $pics .= $this->generateImageCell($lang, 'left', $visit, $visitID, $markers, $media, $mediaList[4], $imt_value_left, $imt_count_left, '6L');
  498. $pics .= '</tr>';
  499. $pics .= '<tr><td colspan="4" style="height: 16px;"></td></tr>'.'<tr>';
  500. $pics .= $this->generateImageCell($lang, 'right', $visit, $visitID, $markers, $media, $mediaList[2], $imt_value_right, $imt_count_right, '6R');
  501. $pics .= $this->generateImageCell($lang, 'left', $visit, $visitID, $markers, $media, $mediaList[5], $imt_value_left, $imt_count_left, '6L');
  502. $pics .= '</tr>';
  503. }
  504. $pics .= '</table>';
  505. $brand = 'data:image/' . pathinfo(parse_url('../../storage/logo/logo-48.png', PHP_URL_PATH), PATHINFO_EXTENSION) . ';base64,' . base64_encode(file_get_contents('../../storage/logo/logo-48.png'));
  506. $page1 =
  507. '<div class="page-break">'.
  508. //'<h1>'.$this->tr($lang, $reportType==2?'title2':'title6').'</h1>'.
  509. '<h3 style="margin:4px 0;">'.$org_name.'</h3>'.
  510. '<h3 style="font-size:14px;">'.$org_address.'</h3>'.
  511. '<table style="width: 100%; border: 1px solid black;" border="0">'.
  512. '<tr><td>'.$this->tr($lang, 'patientID').':</td><td>'.$patient['patientID'].'</td><td>'.$this->tr($lang, 'patientName').':</td><td>'.$patient['lastname'].' '.$patient['firstname'].'</td></tr>'.
  513. '<tr><td>'.$this->tr($lang, 'patientBirth').':</td><td>'.$this->trDate($lang, $patient['birthDate']).'</td><td>'.$this->tr($lang, 'visitID').':</td><td>'.$patient['number'].'</td></tr>'.
  514. '<tr><td>'.$this->tr($lang, 'institution').':</td><td>'.$user['name'].'</td><td>'.$this->tr($lang, 'physician').':</td><td>'.$user['lastname'].' '.$user['firstname'].'</td></tr>'.
  515. '</table>'.
  516. $pics.
  517. '<div class="footer">'.
  518. '<div style="position:absolute; left:0;">'.$patient['lastname'].' '.$patient['firstname'].'</div>'.
  519. $this->trDate($lang, date('Y-m-d')).
  520. '<div style="position:absolute; right:0;"><img src="'.$brand.'" style="width:96px;"></div>'.
  521. '</div>'.
  522. '</div>';
  523. // 2nd page
  524. if($imt_count_left>0 && $imt_count_right>0) {
  525. $password = strtoupper(substr($patient['firstname'], 0, 1)).strtoupper(substr($patient['lastname'], 0, 1)).substr($patient['birthDate'],0,4);
  526. if($imt_count_left>0) {
  527. $imt_value_left /= $imt_count_left;
  528. }
  529. if($imt_count_right>0) {
  530. $imt_value_right /= $imt_count_right;
  531. }
  532. $abacus_name = '';
  533. $abacus_gender = $patient['gender'];
  534. if($data['abacus']=='PARC') {
  535. $abacus_name = 'Europe, France, 30-79 years, PARC 2009 (Mean)';
  536. }
  537. else if($data['abacus']=='ARIC_CAUCASIAN') {
  538. $abacus_name = 'USA, 35-85 years, Caucasian, ARIC 1993 (R+L). Extrapolated values below 45 and over 65';
  539. }
  540. else if($data['abacus']=='ARIC_AFRICAN') {
  541. $abacus_name = 'USA, 35-85 years, African American, ARIC 1993 (R+L). Extrapolated values below 45 and over 65';
  542. }
  543. // graph layout
  544. $graph_layout = '';
  545. $abacusTable = '';
  546. if($data['abacus']=='PARC') {
  547. // no matter the side
  548. $statement = $this->DataInterface->DatabaseConnection->prepare(
  549. "SELECT data FROM abacus WHERE name = '$abacus_name' AND sex = '$abacus_gender'"
  550. );
  551. if(!$statement->execute()) {
  552. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  553. }
  554. $abacus_raw_data = json_decode($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['data']);
  555. $abacus_data = [];
  556. foreach($abacus_raw_data as $raw_data) {
  557. $abacus_data[$raw_data->age] = [$raw_data->min, $raw_data->mean, $raw_data->max];
  558. }
  559. // ref values
  560. $abacusTable .= '<tr style="background-color: lightgray;"><td>'.$this->tr($lang, 'age').'</td><td>'.$this->tr($lang, '25percent').'</td><td>'.$this->tr($lang, '50percent').'</td><td>'.$this->tr($lang, '75percent').'</td></tr>';
  561. foreach($abacus_data as $age => $values) {
  562. $abacusTable .= '<tr><td>'.$age.'</td>';
  563. foreach($values as $value) {
  564. $abacusTable .= '<td>'.$value.'</td>';
  565. }
  566. $abacusTable .= '</tr>';
  567. }
  568. // imt value
  569. $imt_value = $imt_value_left + $imt_value_right;
  570. if($imt_value_left>0 && $imt_value_right>0) {
  571. $imt_value /= 2.0;
  572. }
  573. // graph
  574. $graph_layout .= '<img src="'.$this->generateGraph2($abacus_data, 0, $lang, $imt_value, $patient['birthDate']).'">';
  575. }
  576. else if($data['abacus']=='ARIC_CAUCASIAN' || $data['abacus']=='ARIC_AFRICAN') {
  577. // right
  578. $statement = $this->DataInterface->DatabaseConnection->prepare(
  579. "SELECT data FROM abacus WHERE name = '$abacus_name' AND sex = '$abacus_gender' AND side = 'right'"
  580. );
  581. if(!$statement->execute()) {
  582. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  583. }
  584. $abacus_raw_data = json_decode($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['data']);
  585. $abacus_data_right = [];
  586. foreach($abacus_raw_data as $raw_data) {
  587. $abacus_data_right[$raw_data->age] = [$raw_data->min, $raw_data->mean, $raw_data->max];
  588. }
  589. // left
  590. $statement = $this->DataInterface->DatabaseConnection->prepare(
  591. "SELECT data FROM abacus WHERE name = '$abacus_name' AND sex = '$abacus_gender' AND side = 'left'"
  592. );
  593. if(!$statement->execute()) {
  594. return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()];
  595. }
  596. $abacus_raw_data = json_decode($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['data']);
  597. $abacus_data_left = [];
  598. foreach($abacus_raw_data as $raw_data) {
  599. $abacus_data_left[$raw_data->age] = [$raw_data->min, $raw_data->mean, $raw_data->max];
  600. }
  601. // ref values
  602. $abacusTable .= '<tr style="background-color: lightgray;"><td rowspan="2">'.$this->tr($lang, 'age').'</td><td colspan="2">'.$this->tr($lang, '25percent').'</td><td colspan="2">'.$this->tr($lang, '50percent').'</td><td colspan="2">'.$this->tr($lang, '75percent').'</td></tr>';
  603. $abacusTable .= '<tr style="background-color: lightgray;"><td>R</td><td>L</td><td>R</td><td>L</td><td>R</td><td>L</td></tr>';
  604. for($i=0; $i<count($abacus_data_right); $i++) {
  605. $k = array_keys($abacus_data_right)[$i];
  606. $abacusTable .= '<tr><td>'.$k.'</td>';
  607. $val_right = $abacus_data_right[$k];
  608. $val_left = $abacus_data_left[$k];
  609. for($v=0; $v<count($val_right); $v++) {
  610. $abacusTable .= '<td>'.$val_right[$v].'</td>';
  611. $abacusTable .= '<td>'.$val_left[$v].'</td>';
  612. }
  613. $abacusTable .= '</tr>';
  614. }
  615. // graph
  616. $graph_layout .= '<table style="width: 100%;" border="0">';
  617. $graph_layout .= '<tr><td style="width: 50%;">';
  618. $graph_layout .= '<img style="width: 100%;" src="'.$this->generateGraph2($abacus_data_right, 0.5, $lang, $imt_value_right, $patient['birthDate']).'">';
  619. $graph_layout .= '</td><td style="width: 50%;">';
  620. $graph_layout .= '<img style="width: 100%;" src="'.$this->generateGraph2($abacus_data_left, 0.5, $lang, $imt_value_left, $patient['birthDate']).'">';
  621. $graph_layout .= '</td></tr>';
  622. $graph_layout .= '</table>';
  623. }
  624. $page2 =
  625. '<div class="page-break">'.
  626. '<h3>'.$this->tr($lang, 'p2_title').' ('.$this->tr($lang, $patient['gender']).')</h3>'.
  627. '<table class="grid" style="width: 100%; margin-bottom: 16px; font-size: 12px;" border="0">'.
  628. $abacusTable.
  629. '</table>'.
  630. '<p style="width: 100%; text-align: center; margin-bottom: 16px; font-size: 12px;">'.$this->tr($lang, $data['abacus']).'</p>'.
  631. '<p style="width: 100%;" align="center">'.
  632. $graph_layout.
  633. '</p>'.
  634. '<h5 style="margin:0; padding-top: 0;">'.$this->tr($lang, 'indications').'</h4>'.
  635. '<p style="height:20px;">'.$data['indications'].'</p>'.
  636. '<h5 style="margin:0;">'.$this->tr($lang, 'clinical').'</h4>'.
  637. '<p style="height:20px;">'.$data['clinical'].'</p>'.
  638. '<h5 style="margin:0;">'.$this->tr($lang, 'report').'</h4>'.
  639. '<p style="height:160px;">'.$data['report'].'</p>'.
  640. '<p style="width: 100%; text-align: center; font-size: 12px;">'.$this->tr($lang, 'disclaimer').'</p>'.
  641. '<div class="footer">'.
  642. '<div style="position:absolute; left:0;">'.$password.'</div>'.
  643. $this->trDate($lang, date('Y-m-d')).
  644. '<div style="position:absolute; right:0;"><img src="'.$brand.'" style="width:96px;"></div>'.
  645. '</div>'.
  646. '</div>';
  647. }
  648. // 3rd page
  649. $page3 =
  650. '<div class="">'.
  651. '<h3 class="blue">'.$this->tr($lang, 'p3_title').'</h3>'.
  652. '<h4 class="blue">'.$this->tr($lang, 'p3_subtitle1').'</h4>'.
  653. '<p>'.$this->tr($lang, 'p3_text1').'</p>'.
  654. '<h4 class="blue">'.$this->tr($lang, 'p3_subtitle2').'</h4>'.
  655. '<p>'.$this->tr($lang, 'p3_text2').'</p>'.
  656. '<h4 class="blue">'.$this->tr($lang, 'p3_subtitle3').'</h4>'.
  657. '<p>'.$this->tr($lang, 'p3_text3').'</p>'.
  658. '<h4 class="blue">'.$this->tr($lang, 'p3_subtitle4').'</h4>'.
  659. '<p>'.$this->tr($lang, 'p3_text4').'</p>'.
  660. '<h4 class="blue">'.$this->tr($lang, 'p3_subtitle5').'</h4>'.
  661. '<p>'.$this->tr($lang, 'p3_text5').'</p>'.
  662. '<div class="footer">'.
  663. '<div style="position:absolute; left:0;">'.$patient['lastname'].' '.$patient['firstname'].'</div>'.
  664. $this->trDate($lang, date('Y-m-d')).
  665. '<div style="position:absolute; right:0;"><img src="'.$brand.'" style="width:96px;"></div>'.
  666. '</div>'.
  667. '</div>';
  668. $html = '<html><style>'.$style.'</style><body>'.$page1.$page2.$page3.'</body></html>';
  669. //file_put_contents('test.html', $html);
  670. $filename = $patient['lastname'].' '.$patient['firstname'].'-'.$visitID;
  671. $filename = str_replace(' ','_',$filename);
  672. $options = new \Dompdf\Options();
  673. $options->set('isRemoteEnabled', false);
  674. $dompdf = new \Dompdf\Dompdf($options);
  675. $dompdf->loadHtml($html, 'UTF-8');
  676. $dompdf->setPaper('A4', 'portrait');
  677. $dompdf->render();
  678. //$dompdf->get_canvas()->get_cpdf()->setEncryption($password, $password);
  679. $baseDir = '../../storage/report/';
  680. \Tools\FS::mkpath($baseDir);
  681. $path = $baseDir.$filename.'.pdf';
  682. file_put_contents($path, $dompdf->output());
  683. $reportBase64 = base64_encode(file_get_contents($path));
  684. return [
  685. 'result' => 'OK',
  686. 'data' => $data,
  687. 'media' => $media,
  688. 'password' => $password,
  689. 'base64' => $reportBase64,
  690. 'filename' => $filename.'.pdf',
  691. 'patient' => $patient,
  692. 'visit' => $visit
  693. ];
  694. }
  695. public function tr($lang, $item) {
  696. return array_key_exists($item, DataInterface::$translation[$lang])?DataInterface::$translation[$lang][$item]:'?'.$item.'?';
  697. }
  698. public function trDate($lang, $date) {
  699. if($lang=='fr') {
  700. $t = explode('-', $date);
  701. return $t[2].'/'.$t[1].'/'.$t[0];
  702. }
  703. return $date;
  704. }
  705. protected function generateGraph2($abacus, $plot_offset, $lang, $imt_value, $birth) {
  706. $width = 450;
  707. $height = 300;
  708. $margin = 40;
  709. $margin2 = 20;
  710. $today = date("Y-m-d");
  711. $diff = date_diff(date_create($birth), date_create($today));
  712. $patientAge = $diff->y;
  713. $png_image = imagecreate($width+$margin+$margin+$margin2, $height+$margin);
  714. imagecolorallocate($png_image, 255, 255, 255);
  715. imageantialias($png_image, true);
  716. //imagesetthickness($png_image, 2);
  717. $white = imagecolorallocate($png_image, 255, 255, 255);
  718. $black = imagecolorallocate($png_image, 0, 0, 0);
  719. $red = imagecolorallocate($png_image, 255, 0, 0);
  720. $green = imagecolorallocate($png_image, 0, 128, 0);
  721. $blue = imagecolorallocate($png_image, 0, 0, 128);
  722. // scale
  723. $yr = 125 - 40;
  724. $yr2 = $yr / 5 * 2;
  725. $xr = 90 - 20;
  726. $xr2 = $xr / 5;
  727. // Y => 30 / 29 => 34 => 33
  728. $val = 0.4;
  729. imageline($png_image, $margin2+$margin, 0, $margin2+$margin, $height, $black);
  730. for($i=0; $i<=$yr2; $i++) {
  731. $y = $height - (($height-0)/$yr2 * $i);
  732. $offset = ($i%2)?2:5;
  733. imageline($png_image, $margin2+$margin-$offset, 0+$y, $margin2+$margin+$offset, 0+$y, $black);
  734. if(!($i%2) && $i<$yr2-1) {
  735. imagestring($png_image, 2, $margin2+$margin-32, $y-6, number_format($val,2), $black);
  736. $val+=0.05;
  737. }
  738. }
  739. // X
  740. $age = 20;
  741. imageline($png_image, $margin2+$margin, $height, $margin2+$width+$margin, $height, $black);
  742. for($i=0; $i<=$xr2; $i++) {
  743. $x = ($width-0)/$xr2 * $i;
  744. $offset = ($i%2)?2:5;
  745. imageline($png_image, $margin2+$margin+$x, $height-$offset, $margin2+$margin+$x, $height+$offset, $black);
  746. if(!($i%2)) {
  747. imagestring($png_image, 2, $margin2+$margin+$x-6, $height+6, $age, $black);
  748. $age+=10;
  749. }
  750. }
  751. // 25%
  752. $tab = [];
  753. foreach($abacus as $age => $values) {
  754. $tab[] = $values[0];
  755. }
  756. for($i=0; $i<count($tab)-1; $i++) {
  757. $x1 = ($width-1)/($xr2/2) * (1+$i+$plot_offset);
  758. $x2 = ($width-1)/($xr2/2) * (1+$i+1+$plot_offset);
  759. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  760. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  761. imageline($png_image, $margin2+$margin+$x1, $y1, $margin2+$margin+$x2, $y2, $green);
  762. imagefilledellipse($png_image, $margin2+$margin+$x1, $y1, 5, 5, $green);
  763. }
  764. imagefilledellipse($png_image, $margin2+$margin+(($width-1)/($xr2/2) * (1+count($tab)-1+$plot_offset)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $green);
  765. imagefilledrectangle($png_image, $margin2+$margin+40, 0, $margin2+$margin+50, 10, $green);
  766. imagettftext($png_image, 10, 0, $margin2+$margin+50+5, 10, $green, './arial.ttf', $this->tr($lang, '25percent'));
  767. // 50%
  768. $tab = [];
  769. foreach($abacus as $age => $values) {
  770. $tab[] = $values[1];
  771. }
  772. imagesetstyle($png_image, array($black, $black, $black, $black, $black, $black, $black, $black, $black, $black, $white, $white, $white, $white, $white, $white, $white, $white, $white, $white));
  773. for($i=0; $i<count($tab)-1; $i++) {
  774. $x1 = ($width-1)/($xr2/2) * (1+$i+$plot_offset);
  775. $x2 = ($width-1)/($xr2/2) * (1+$i+1+$plot_offset);
  776. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  777. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  778. imageline($png_image, $margin2+$margin+$x1, $y1, $margin2+$margin+$x2, $y2, IMG_COLOR_STYLED);
  779. imagefilledellipse($png_image, $margin2+$margin+$x1, $y1, 5, 5, $black);
  780. }
  781. imagefilledellipse($png_image, $margin2+$margin+(($width-1)/($xr2/2) * (1+count($tab)-1+$plot_offset)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $black);
  782. imagefilledrectangle($png_image, $margin2+$margin+180, 0, $margin2+$margin+190, 10, $black);
  783. imagettftext($png_image, 10, 0, $margin2+$margin+190+5, 10, $black, './arial.ttf', $this->tr($lang, '50percent'));
  784. // 75%
  785. $tab = [];
  786. foreach($abacus as $age => $values) {
  787. $tab[] = $values[2];
  788. }
  789. for($i=0; $i<count($tab)-1; $i++) {
  790. $x1 = ($width-1)/($xr2/2) * (1+$i+$plot_offset);
  791. $x2 = ($width-1)/($xr2/2) * (1+$i+1+$plot_offset);
  792. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  793. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  794. imageline($png_image, $margin2+$margin+$x1, $y1, $margin2+$margin+$x2, $y2, $red);
  795. imagefilledellipse($png_image, $margin2+$margin+$x1, $y1, 5, 5, $red);
  796. }
  797. imagefilledellipse($png_image, $margin2+$margin+(($width-1)/($xr2/2) * (1+count($tab)-1+$plot_offset)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $red);
  798. imagefilledrectangle($png_image, $margin2+$margin+320, 0, $margin2+$margin+330, 10, $red);
  799. imagettftext($png_image, 10, 0, $margin2+$margin+330+5, 10, $red, './arial.ttf', $this->tr($lang, '75percent'));
  800. // value
  801. $x = $margin+($width/($xr) * ($patientAge-20));
  802. $y = $height - ( $height / $yr * ($imt_value*100-40) );
  803. imagefilledellipse($png_image, $margin2+$x, $y, 9, 9, $blue);
  804. imagettftext($png_image, 10, 0, $margin2+$x + 6, $y + 5, $blue, './arial.ttf', number_format($imt_value,3));
  805. // legends
  806. imagettftext($png_image, 10, 0, $width/2+5, $height+32, $black, './arial.ttf', 'Age (y)');
  807. imagettftext($png_image, 10, 90, 10, $height/2-5, $black, './arial.ttf', 'IMT (mm)');
  808. ob_start();
  809. imagepng($png_image);
  810. $data = ob_get_contents();
  811. ob_end_clean();
  812. $res = 'data:image/png;base64,'.base64_encode($data);
  813. imagedestroy($png_image);
  814. return $res;
  815. }
  816. protected function generateGraph($ABQ, $abacus, $lang, $imt_value_left, $imt_value_right, $birth) {
  817. $width = 450;
  818. $height = 300;
  819. $margin = 40;
  820. $today = date("Y-m-d");
  821. $diff = date_diff(date_create($birth), date_create($today));
  822. $patientAge = $diff->y;
  823. $png_image = imagecreate($width+$margin+$margin, $height+$margin);
  824. imagecolorallocate($png_image, 255, 255, 255);
  825. imageantialias($png_image, true);
  826. //imagesetthickness($png_image, 2);
  827. $white = imagecolorallocate($png_image, 255, 255, 255);
  828. $black = imagecolorallocate($png_image, 0, 0, 0);
  829. $red = imagecolorallocate($png_image, 255, 0, 0);
  830. $green = imagecolorallocate($png_image, 0, 128, 0);
  831. $blue = imagecolorallocate($png_image, 0, 0, 128);
  832. // value
  833. $imt_value = $imt_value_left + $imt_value_right;
  834. if($imt_value_left>0 && $imt_value_right>0) {
  835. $imt_value /= 2.0;
  836. }
  837. // Y
  838. $val = 0.4;
  839. imageline($png_image, $margin, 0, $margin, $height, $black);
  840. for($i=0; $i<=30; $i++) {
  841. $y = $height - (($height-0)/30 * $i);
  842. $offset = ($i%2)?2:5;
  843. imageline($png_image, $margin-$offset, 0+$y, $margin+$offset, 0+$y, $black);
  844. if(!($i%2) && $i<29) {
  845. imagestring($png_image, 2, $margin-32, $y-6, number_format($val,2), $black);
  846. $val+=0.05;
  847. }
  848. }
  849. //
  850. $yr = 115 - 40;
  851. if($ABQ=='PARC') {
  852. // X
  853. $age = 20;
  854. imageline($png_image, $margin, $height, $width+$margin, $height, $black);
  855. for($i=0; $i<=12; $i++) {
  856. $x = ($width-0)/12 * $i;
  857. $offset = ($i%2)?2:5;
  858. imageline($png_image, $margin+$x, $height-$offset, $margin+$x, $height+$offset, $black);
  859. if(!($i%2)) {
  860. imagestring($png_image, 2, $margin+$x-6, $height+6, $age, $black);
  861. $age+=10;
  862. }
  863. }
  864. // 25%
  865. $tab = [];
  866. foreach($abacus as $age => $values) {
  867. $tab[] = $values[0];
  868. }
  869. for($i=0; $i<count($tab)-1; $i++) {
  870. $x1 = ($width-1)/6 * (1+$i);
  871. $x2 = ($width-1)/6 * (1+$i+1);
  872. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  873. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  874. imageline($png_image, $margin+$x1, $y1, $margin+$x2, $y2, $green);
  875. imagefilledellipse($png_image, $margin+$x1, $y1, 5, 5, $green);
  876. }
  877. imagefilledellipse($png_image, $margin+(($width-1)/6 * (1+count($tab)-1)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $green);
  878. imagefilledrectangle($png_image, $margin+40, 0, $margin+50, 10, $green);
  879. imagettftext($png_image, 10, 0, $marvisitgin+50+5, 10, $green, './arial.ttf', $this->tr($lang, '25percent'));
  880. // 50%
  881. $tab = [];
  882. foreach($abacus as $age => $values) {
  883. $tab[] = $values[1];
  884. }
  885. imagesetstyle($png_image, array($black, $black, $black, $black, $black, $black, $black, $black, $black, $black, $white, $white, $white, $white, $white, $white, $white, $white, $white, $white));
  886. for($i=0; $i<count($tab)-1; $i++) {
  887. $x1 = ($width-1)/6 * (1+$i);
  888. $x2 = ($width-1)/6 * (1+$i+1);
  889. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  890. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  891. imageline($png_image, $margin+$x1, $y1, $margin+$x2, $y2, IMG_COLOR_STYLED);
  892. imagefilledellipse($png_image, $margin+$x1, $y1, 5, 5, $black);
  893. }
  894. imagefilledellipse($png_image, $margin+(($width-1)/6 * (1+count($tab)-1)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $black);
  895. imagefilledrectangle($png_image, $margin+180, 0, $margin+190, 10, $black);
  896. imagettftext($png_image, 10, 0, $margin+190+5, 10, $black, './arial.ttf', $this->tr($lang, '50percent'));
  897. // 75%
  898. $tab = [];
  899. foreach($abacus as $age => $values) {
  900. $tab[] = $values[2];
  901. }
  902. for($i=0; $i<count($tab)-1; $i++) {
  903. $x1 = ($width-1)/6 * (1+$i);
  904. $x2 = ($width-1)/6 * (1+$i+1);
  905. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  906. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  907. imageline($png_image, $margin+$x1, $y1, $margin+$x2, $y2, $red);
  908. imagefilledellipse($png_image, $margin+$x1, $y1, 5, 5, $red);
  909. }
  910. imagefilledellipse($png_image, $margin+(($width-1)/6 * (1+count($tab)-1)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $red);
  911. imagefilledrectangle($png_image, $margin+320, 0, $margin+330, 10, $red);
  912. imagettftext($png_image, 10, 0, $margin+330+5, 10, $red, './arial.ttf', $this->tr($lang, '75percent'));
  913. // value
  914. $x = $margin+($width/(80-20) * ($patientAge-20));
  915. $y = $height - ( $height / $yr * ($imt_value*100-40) );
  916. imagefilledellipse($png_image, $x, $y, 9, 9, $blue);
  917. imagettftext($png_image, 10, 0, $x + 6, $y + 5, $blue, './arial.ttf', number_format($imt_value,3));
  918. }
  919. else if($ABQ=='VITA') {
  920. // X
  921. $age = 35;
  922. imageline($png_image, $margin, $height, $width+$margin, $height, $black);
  923. for($i=0; $i<=12; $i++) {
  924. $x = ($width-0)/12 * $i;
  925. $offset = ($i%2)?2:5;
  926. imageline($png_image, $margin+$x, $height-$offset, $margin+$x, $height+$offset, $black);
  927. if(!($i%2)) {
  928. imagestring($png_image, 2, $margin+$x-6, $height+6, $age, $black);
  929. $age+=5;
  930. }
  931. }
  932. // 20%
  933. $tab = [];
  934. foreach($abacus as $age => $values) {
  935. $tab[] = $values[0];
  936. }
  937. for($i=0; $i<count($tab)-1; $i++) {
  938. $x1 = ($width-1)/6 * (2+$i);
  939. $x2 = ($width-1)/6 * (2+$i+1);
  940. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  941. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  942. imageline($png_image, $margin+$x1, $y1, $margin+$x2, $y2, $green);
  943. imagefilledellipse($png_image, $margin+$x1, $y1, 5, 5, $green);
  944. }
  945. imagefilledellipse($png_image, $margin+(($width-1)/6 * (2+count($tab)-1)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $green);
  946. imagefilledrectangle($png_image, $margin+40, 0, $margin+50, 10, $green);
  947. imagettftext($png_image, 10, 0, $margin+50+5, 10, $green, './arial.ttf', $this->tr($lang, '20percent'));
  948. // 80%
  949. $tab = [];
  950. foreach($abacus as $age => $values) {
  951. $tab[] = $values[1];
  952. }
  953. for($i=0; $i<count($tab)-1; $i++) {
  954. $x1 = ($width-1)/6 * (2+$i);
  955. $x2 = ($width-1)/6 * (2+$i+1);
  956. $y1 = $height - ( $height / $yr * ($tab[$i]*100-40) );
  957. $y2 = $height - ( $height / $yr * ($tab[$i+1]*100-40) );
  958. imageline($png_image, $margin+$x1, $y1, $margin+$x2, $y2, $red);
  959. imagefilledellipse($png_image, $margin+$x1, $y1, 5, 5, $red);
  960. }
  961. imagefilledellipse($png_image, $margin+(($width-1)/6 * (2+count($tab)-1)), $height - ( $height / $yr * ($tab[count($tab)-1]*100-40) ), 5, 5, $red);
  962. imagefilledrectangle($png_image, $margin+180, 0, $margin+190, 10, $red);
  963. imagettftext($png_image, 10, 0, $margin+190+5, 10, $red, './arial.ttf', $this->tr($lang, '80percent'));
  964. // value
  965. $x = $margin+($width/(65-35) * ($patientAge-35));
  966. $y = $height - ( $height / $yr * ($imt_value*100-40) );
  967. imagefilledellipse($png_image, $x, $y, 9, 9, $blue);
  968. imagettftext($png_image, 10, 0, $x + 6, $y + 5, $blue, './arial.ttf', number_format($imt_value,3));
  969. }
  970. ob_start();
  971. imagepng($png_image);
  972. $data = ob_get_contents();
  973. ob_end_clean();
  974. $res = 'data:image/png;base64,'.base64_encode($data);
  975. imagedestroy($png_image);
  976. return $res;
  977. }
  978. }
  979. }