DataInterface = $DataInterface; } /** * Get media */ public function measureGet($User, $patientID, $visitID) { // patient $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT patient.* FROM patient WHERE ID = :fk_patient" ); $statement->bindParam(':fk_patient', $patientID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $patient = $statement->fetchAll(\PDO::FETCH_ASSOC)[0]; // visit $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT area, markers FROM visit WHERE ID = :fk_visit" ); $statement->bindParam(':fk_visit', $visitID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $data = $statement->fetchAll(\PDO::FETCH_ASSOC)[0]; // media $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT ID, side, location, filename, metrics FROM media WHERE fk_visit = :fk_visit" ); $statement->bindParam(':fk_visit', $visitID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $media = $statement->fetchAll(\PDO::FETCH_ASSOC); foreach($media as &$m) { $m['metrics'] = json_decode($m['metrics']); // measures $m['measure'] = $this->getMeasures($m['ID']); } // settings $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT anon_percent FROM clinical_trial" ); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $anon_percent = intval($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['anon_percent']); return [ 'result' => 'OK', 'patient' => $patient, 'area' => $data['area'], 'markers' => json_decode($data['markers']), 'media' => $media, 'anon_percent' => $anon_percent ]; } /** * Complete */ public function measureCompletePost($User, $data) { $userID = $User->ID; $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT password FROM user WHERE ID = $userID" ); if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $securedPassword = $statement->fetchAll(\PDO::FETCH_ASSOC)[0]['password']; if(\Tools\Crypto::verify($data['password'], $securedPassword)) { // update $statement = $this->DataInterface->DatabaseConnection->prepare( "UPDATE visit SET completed = NOW() WHERE ID = :visitID" ); $statement->bindParam(':visitID', $data['visitID']); if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } } else { return ['result' => 'ERROR', 'reason' => 'bad_password', 'message' => 'Invalid password']; } return [ 'result' => 'OK', ]; } /** * */ protected function getMeasures($fk_media) { // measures $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT * FROM measure WHERE fk_media = :fk_media ORDER BY ID DESC" ); $statement->bindParam(':fk_media', $fk_media); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $measures = $statement->fetchAll(\PDO::FETCH_ASSOC); foreach($measures as &$measure) { $measure['points'] = json_decode($measure['points']); $measure['computation'] = json_decode($measure['computation']); } return $measures; } /** * */ protected function useCredit($fk_user, $fk_media) { // user type $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT type FROM user WHERE ID = :fk_user" ); $statement->bindParam(':fk_user', $fk_user); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $userType = $statement->fetchAll(\PDO::FETCH_ASSOC)[0]['type']; $targetID = $fk_user; if($userType=='reader') { // customer CRO $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT * FROM user WHERE type = 'cro' ORDER BY ID LIMIT 0,1" ); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $customer = $statement->fetchAll(\PDO::FETCH_ASSOC); $targetID = $customer[0]['ID']; } // total purchased credits $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT SUM(count) AS purchased FROM credit WHERE ID_user = :fk_user" ); $statement->bindParam(':fk_user', $targetID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $purchased = intval($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['purchased']); // total used credits $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT COUNT(ID) AS used FROM credit_usage WHERE fk_user = :fk_user" ); $statement->bindParam(':fk_user', $targetID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $used = intval($statement->fetchAll(\PDO::FETCH_ASSOC)[0]['used']); if($purchased-$used>0) { // usage on this media $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT ID FROM credit_usage WHERE fk_media = :fk_media AND fk_user = :fk_user" ); $statement->bindParam(':fk_media', $fk_media); $statement->bindParam(':fk_user', $targetID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } // not used yet $media_credit = $statement->fetchAll(\PDO::FETCH_ASSOC); if(count($media_credit)==0) { $statement = $this->DataInterface->DatabaseConnection->prepare( "INSERT INTO credit_usage(fk_user, fk_media) VALUES(:fk_user, :fk_media)" ); $statement->bindParam(':fk_user', $targetID); $statement->bindParam(':fk_media', $fk_media); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } return $purchased-$used-1; } return $purchased-$used; } return false; } /** * */ protected function cleanMeasures($fk_media, $keepPlaques, $imtType) { if($keepPlaques) { $keepPlaques = "AND type != 'plaque'"; } else { $keepPlaques = ''; } if($imtType===false) { // clean measures $statement = $this->DataInterface->DatabaseConnection->prepare( "DELETE FROM measure WHERE fk_media = :fk_media AND type != 'calibration' $keepPlaques" ); $statement->bindParam(':fk_media', $fk_media); } else { // clean 'imt' of same type $typeNearWall = ($imtType=='near'?'true':'false'); $statement = $this->DataInterface->DatabaseConnection->prepare( "DELETE FROM measure WHERE fk_media = :fk_media AND type = 'imt' AND JSON_EXTRACT(computation, \"$.nearWall\") = $typeNearWall" ); $statement->bindParam(':fk_media', $fk_media); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } // clean measures but 'imt' $statement = $this->DataInterface->DatabaseConnection->prepare( "DELETE FROM measure WHERE fk_media = :fk_media AND type != 'calibration' $keepPlaques AND type != 'imt'" ); $statement->bindParam(':fk_media', $fk_media); } // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } return true; } /** * */ public function generateFrame($data, $fps, $frame, &$filename) { $filename = $data['filename']; // video if($fps) { if(strpos($filename, '.dicom')!==false) { $filename = str_replace('.dicom','-'.$frame.'.jpeg', $filename); $dst = '../../storage/media/'.$data['visitID'].'/'.$filename; if(!file_exists($dst)) { $src = '../../storage/media/'.$data['visitID'].'/'.$data['filename']; $cmdLine = 'dcmj2pnm "'.$src.'" "'.$dst.'" +oj +Jq 100 +F '.($frame+1).' -v 2>&1'; $output=null; $retval=null; exec($cmdLine, $output, $retval); // error if($retval !== 0 || count($output)<1) { return [ 'result' => 'ERROR', 'cmdLine' => $cmdLine, 'output' => $output, 'retval' => $retval ]; } } } else { $filename = str_replace('.mp4','-'.$frame.'.jpeg', $filename); $dst = '../../storage/media/'.$data['visitID'].'/'.$filename; if(!file_exists($dst)) { $ss = $frame; $fps = intval($fps); if($frame != 0) { $t = floor($frame / $fps); $r = $frame - ($t * $fps); $ss = $t + ($r * 1.0 / $fps); } $src = '../../storage/media/'.$data['visitID'].'/'.$data['filename']; $cmdLine = 'ffmpeg -ss '.$ss.' -i '.$src.' -q:v 1 -frames:v 1 '.$dst; $output=null; $retval=null; exec($cmdLine, $output, $retval); // error if($retval !== 0 || count($output)!==0) { return [ 'result' => 'ERROR', 'cmdLine' => $cmdLine, 'output' => $output, 'retval' => $retval ]; } } } } // image else { // dicom if(strpos($filename, '.dicom')!==false) { $filename = str_replace('.dicom','.jpeg', $filename); } } return true; } /** * */ public function measureCalibrationPost($User, $data) { $userID = $User->ID; // calibration $statement = $this->DataInterface->DatabaseConnection->prepare( "UPDATE media SET metrics = JSON_SET(metrics, \"$.pxwidth\", :scale1+0, \"$.pxheight\", :scale2+0) WHERE filename = :filename AND fk_visit = :fk_visit" ); $statement->bindParam(':filename', $data['filename']); $statement->bindParam(':fk_visit', $data['visitID']); $statement->bindParam(':scale1', $data['scale']); $statement->bindParam(':scale2', $data['scale']); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } // media $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT ID, JSON_EXTRACT(metrics, \"$.fps\") AS fps FROM media WHERE filename = :filename AND fk_visit = :fk_visit" ); $statement->bindParam(':filename', $data['filename']); $statement->bindParam(':fk_visit', $data['visitID']); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $fk_media = $statement->fetchAll(\PDO::FETCH_ASSOC)[0]['ID']; // Gen frame? $fps = $results[0]['fps']; $frame = intval($data['frame']); $filename = $data['filename']; $r = $this->generateFrame($data, $fps, $frame, $filename); if($r !== true) { return $r; } // clean measure $statement = $this->DataInterface->DatabaseConnection->prepare( "DELETE FROM measure WHERE fk_media = :fk_media" ); $statement->bindParam(':fk_media', $fk_media); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } // insert measure $statement = $this->DataInterface->DatabaseConnection->prepare( "INSERT INTO measure VALUES(0, 'calibration', :points, :computation, :fk_media, :frame, :fk_user, NOW())" ); $points = json_encode($data['points'], JSON_NUMERIC_CHECK); $statement->bindParam(':points', $points); $computation = json_encode($data['computation'], JSON_NUMERIC_CHECK); $statement->bindParam(':computation', $computation); $statement->bindParam(':fk_media', $fk_media); $statement->bindParam(':frame', $data['frame']); $statement->bindParam(':fk_user', $userID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $measureID = $this->DataInterface->DatabaseConnection->lastInsertId(); return [ 'result' => 'OK', 'scale' => floatval($data['scale']), 'measure' => array( 'ID' => $measureID, 'fk_media' => $fk_media, 'type' => 'calibration', 'points' => json_decode($points), 'frame' => intval($data['frame']), 'computation' => json_decode($computation) ) ]; } /** * */ public function measureDistancePost($User, $data) { $userID = $User->ID; // media $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT ID, JSON_EXTRACT(metrics, \"$.fps\") AS fps FROM media WHERE filename = :filename AND fk_visit = :fk_visit" ); $statement->bindParam(':filename', $data['filename']); $statement->bindParam(':fk_visit', $data['visitID']); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $results = $statement->fetchAll(\PDO::FETCH_ASSOC); if(count($results)==0) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => $data['filename'], 'data' => $statement->errorInfo()]; } $fk_media = $results[0]['ID']; // Gen frame? $fps = $results[0]['fps']; $frame = intval($data['frame']); $filename = $data['filename']; $r = $this->generateFrame($data, $fps, $frame, $filename); if($r !== true) { return $r; } // clean measure $this->cleanMeasures($fk_media, false, false); // insert measure $statement = $this->DataInterface->DatabaseConnection->prepare( "INSERT INTO measure VALUES(0, :type, :points, :computation, :fk_media, :frame, :fk_user, NOW())" ); $statement->bindParam(':type', $data['type']); $points = json_encode($data['points'], JSON_NUMERIC_CHECK); $statement->bindParam(':points', $points); $computation = json_encode($data['computation'], JSON_NUMERIC_CHECK); $statement->bindParam(':computation', $computation); $statement->bindParam(':fk_media', $fk_media); $statement->bindParam(':frame', $data['frame']); $statement->bindParam(':fk_user', $userID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $measureID = $this->DataInterface->DatabaseConnection->lastInsertId(); return [ 'result' => 'OK', 'measure' => array( 'ID' => $measureID, 'fk_media' => $fk_media, 'type' => $data['type'], 'points' => json_decode($points), 'frame' => intval($data['frame']), 'computation' => json_decode($computation) ), 'measures' => $this->getMeasures($fk_media) ]; } /** * */ public function measureAreaPost($User, $data) { $userID = $User->ID; // media $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT ID, JSON_EXTRACT(metrics, \"$.fps\") AS fps FROM media WHERE filename = :filename AND fk_visit = :fk_visit" ); $statement->bindParam(':filename', $data['filename']); $statement->bindParam(':fk_visit', $data['visitID']); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $results = $statement->fetchAll(\PDO::FETCH_ASSOC); if(count($results)==0) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => $data['filename'], 'data' => $statement->errorInfo()]; } $fk_media = $results[0]['ID']; // Gen frame? $fps = $results[0]['fps']; $frame = intval($data['frame']); $filename = $data['filename']; $r = $this->generateFrame($data, $fps, $frame, $filename); if($r !== true) { return $r; } // clean measure $this->cleanMeasures($fk_media, false, false); // insert measure $statement = $this->DataInterface->DatabaseConnection->prepare( "INSERT INTO measure VALUES(0, 'area', :points, :computation, :fk_media, :frame, :fk_user, NOW())" ); $points = json_encode($data['points'], JSON_NUMERIC_CHECK); $statement->bindParam(':points', $points); $computation = json_encode($data['computation'], JSON_NUMERIC_CHECK); $statement->bindParam(':computation', $computation); $statement->bindParam(':fk_media', $fk_media); $statement->bindParam(':frame', $data['frame']); $statement->bindParam(':fk_user', $userID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $measureID = $this->DataInterface->DatabaseConnection->lastInsertId(); return [ 'result' => 'OK', 'measure' => array( 'ID' => $measureID, 'fk_media' => $fk_media, 'type' => 'area', 'points' => json_decode($points), 'frame' => intval($data['frame']), 'computation' => json_decode($computation) ), 'measures' => $this->getMeasures($fk_media) ]; } /** * */ public function measureImtPost($User, $data) { $userID = $User->ID; // media $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT ID, JSON_EXTRACT(metrics, \"$.fps\") AS fps FROM media WHERE filename = :filename AND fk_visit = :fk_visit" ); $statement->bindParam(':filename', $data['filename']); $statement->bindParam(':fk_visit', $data['visitID']); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $results = $statement->fetchAll(\PDO::FETCH_ASSOC); if(count($results)==0) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => $data['filename'], 'data' => $statement->errorInfo()]; } $fk_media = $results[0]['ID']; // use credit $credit_left = $this->useCredit($userID, $fk_media); if($credit_left === false) { return ['result' => 'ERROR', 'reason' => 'no_credit']; } // Gen frame? $fps = $results[0]['fps']; $frame = intval($data['frame']); $filename = $data['filename']; $r = $this->generateFrame($data, $fps, $frame, $filename); if($r !== true) { return $r; } $pt0x = $data['points'][0]['x']; $pt0y = $data['points'][0]['y']; $pt1x = $data['points'][1]['x']; $pt1y = $data['points'][1]['y']; $pxwidth = $data['pxwidth']; $pxheight = $data['pxheight']; $relativePath = '../../storage/media/'.$data['visitID'].'/'.$filename; $cmdLine = '../../bin/math-imt -platform offscreen -image='.$relativePath.' -metric='.$pxwidth.'x'.$pxheight.' -point1='.$pt0x.'x'.$pt0y.' -point2='.$pt1x.'x'.$pt1y; $output=null; $retval=null; exec($cmdLine, $output, $retval); if($retval !== 0 || count($output)<1) { return [ 'result' => 'ERROR', 'cmdLine' => $cmdLine, 'output' => $output, 'retval' => $retval ]; } $resComputation = json_decode($output[0]); // clean measure $cln = $this->cleanMeasures($fk_media, false, $resComputation->nearWall?'near':'far'); if($cln !== true) { return $cln; } // insert measure $statement = $this->DataInterface->DatabaseConnection->prepare( "INSERT INTO measure VALUES(0, :type, :points, :computation, :fk_media, :frame, :fk_user, NOW())" ); $type = 'imt'; $statement->bindParam(':type', $type); $points = json_encode($data['points'], JSON_NUMERIC_CHECK); $statement->bindParam(':points', $points); $computation = json_encode($resComputation, JSON_NUMERIC_CHECK); $statement->bindParam(':computation', $computation); $statement->bindParam(':fk_media', $fk_media); $statement->bindParam(':frame', $data['frame']); $statement->bindParam(':fk_user', $userID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $measureID = $this->DataInterface->DatabaseConnection->lastInsertId(); return [ 'result' => 'OK', 'cmdLine' => $cmdLine, 'measure' => array( 'ID' => $measureID, 'fk_media' => $fk_media, 'type' => $type, 'points' => json_decode($points), 'frame' => intval($data['frame']), 'computation' => json_decode($output[0]) ), 'measures' => $this->getMeasures($fk_media), 'credit_left' => $credit_left ]; } /** * */ public function measurePlaquePost($User, $data) { $userID = $User->ID; // media $statement = $this->DataInterface->DatabaseConnection->prepare( "SELECT ID, JSON_EXTRACT(metrics, \"$.fps\") AS fps FROM media WHERE filename = :filename AND fk_visit = :fk_visit" ); $statement->bindParam(':filename', $data['filename']); $statement->bindParam(':fk_visit', $data['visitID']); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $results = $statement->fetchAll(\PDO::FETCH_ASSOC); if(count($results)==0) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => $data['filename'], 'data' => $statement->errorInfo()]; } $fk_media = $results[0]['ID']; // use credit $credit_left = $this->useCredit($userID, $fk_media); if($credit_left === false) { return ['result' => 'ERROR', 'reason' => 'no_credit']; } // Gen frame? $fps = $results[0]['fps']; $frame = intval($data['frame']); $filename = $data['filename']; $r = $this->generateFrame($data, $fps, $frame, $filename); if($r !== true) { return $r; } // clean measure $cln = $this->cleanMeasures($fk_media, $data['keepPlaques']!='false', false); if($cln !== true) { return $cln; } $points = []; for($i=0; $i 'ERROR', 'cmdLine' => $cmdLine, 'output' => $output, 'retval' => $retval ]; } $output = json_decode($output[0]); if(count($output->ptsTrouvesFin)==0) { return [ 'result' => 'ERROR', 'reason' => 'algo_failed' ]; } // insert measure $statement = $this->DataInterface->DatabaseConnection->prepare( "INSERT INTO measure VALUES(0, :type, :points, :computation, :fk_media, :frame, :fk_user, NOW())" ); $type = 'plaque'; $statement->bindParam(':type', $type); $points = json_encode($data['points'], JSON_NUMERIC_CHECK); $statement->bindParam(':points', $points); $computation = json_encode($output, JSON_NUMERIC_CHECK); $statement->bindParam(':computation', $computation); $statement->bindParam(':fk_media', $fk_media); $statement->bindParam(':frame', $data['frame']); $statement->bindParam(':fk_user', $userID); // Error check if(!$statement->execute()) { return ['result' => 'ERROR', 'reason' => 'internal_error', 'message' => 'Database error', 'data' => $statement->errorInfo()]; } $measureID = $this->DataInterface->DatabaseConnection->lastInsertId(); return [ 'result' => 'OK', 'cmdLine' => $cmdLine, 'measure' => array( 'ID' => $measureID, 'fk_media' => $fk_media, 'type' => $type, 'points' => json_decode($points), 'frame' => intval($data['frame']), 'computation' => $output ), 'measures' => $this->getMeasures($fk_media), 'credit_left' => $credit_left ]; } } }