Implement OCR functionality for bib numbers using Google Cloud Vision. Introduce new methods for enabling OCR, processing images, and extracting numbers from OCR text. Update JavaScript to handle cloud-based OCR processing and improve error handling. Increment version code to 4.72.798 to reflect these changes.
This commit is contained in:
@ -122,6 +122,32 @@ switch ($strAction) {
|
||||
$tabRetour = array('state' => 'ok', 'html' => $strForm);
|
||||
break;
|
||||
|
||||
case 'bib_ocr':
|
||||
// MSIN-4401 — Lecture numero dossard via Google Cloud Vision (image JPEG base64).
|
||||
if (!fxInscrGestionBibOcrCloudEnabled()) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'ocr_not_configured');
|
||||
break;
|
||||
}
|
||||
$strImage = (string)($_POST['image'] ?? '');
|
||||
$arrOcr = fxInscrGestionVisionReadBibNumber($strImage);
|
||||
if (!empty($arrOcr['ok']) && !empty($arrOcr['number'])) {
|
||||
$tabRetour = array(
|
||||
'state' => 'ok',
|
||||
'number' => (string)$arrOcr['number'],
|
||||
);
|
||||
break;
|
||||
}
|
||||
$strSeen = '';
|
||||
if (!empty($arrOcr['text'])) {
|
||||
$strSeen = preg_replace('/\D+/', '', (string)$arrOcr['text']);
|
||||
}
|
||||
$tabRetour = array(
|
||||
'state' => 'error',
|
||||
'message' => (string)($arrOcr['message'] ?? 'no_number'),
|
||||
'seen' => $strSeen,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'bib_duplicate_check':
|
||||
// Controle "dossard en double" dans l'evenement (avant enregistrement legacy).
|
||||
// Ne modifie rien : renvoie qui porte deja ce dossard + la politique a appliquer.
|
||||
|
||||
@ -755,7 +755,11 @@
|
||||
if (self.ocrBusy || activeScan !== self) {
|
||||
return;
|
||||
}
|
||||
if (!self.workerReady || !self.video) {
|
||||
if (!self.video) {
|
||||
self.setStatus(scanMsg(self.panel, 'data-msg-loading', 'Initialisation (1re fois)...', 'Initializing (first time)...'), true);
|
||||
return;
|
||||
}
|
||||
if (!cfg.bibOcrCloud && !self.workerReady) {
|
||||
self.setStatus(scanMsg(self.panel, 'data-msg-loading', 'Initialisation (1re fois)...', 'Initializing (first time)...'), true);
|
||||
return;
|
||||
}
|
||||
@ -848,7 +852,10 @@
|
||||
|
||||
BibScanSession.prototype.runOcr = function () {
|
||||
var self = this;
|
||||
if (self.ocrBusy || !self.video || activeScan !== self || !self.workerReady) {
|
||||
if (self.ocrBusy || !self.video || activeScan !== self) {
|
||||
return;
|
||||
}
|
||||
if (!cfg.bibOcrCloud && !self.workerReady) {
|
||||
return;
|
||||
}
|
||||
self.ocrBusy = true;
|
||||
@ -858,6 +865,62 @@
|
||||
}
|
||||
self.setStatus(scanMsg(self.panel, 'data-msg-scanning', 'Lecture en cours...', 'Reading...'), true);
|
||||
|
||||
// MSIN-4401 — Chemin produit : photo → Google Vision (serveur). Plus fiable que Tesseract mobile.
|
||||
if (cfg.bibOcrCloud) {
|
||||
grabOcrCanvas(self).then(function (canvas) {
|
||||
if (!canvas) {
|
||||
throw new Error('capture');
|
||||
}
|
||||
var dataUrl = canvas.toDataURL('image/jpeg', 0.92);
|
||||
return fetch((cfg.domain || '') + '/ajax_inscr_gestion.php', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
|
||||
body: 'a=bib_ocr&lang=' + encodeURIComponent(cfg.lang || 'fr')
|
||||
+ '&image=' + encodeURIComponent(dataUrl)
|
||||
}).then(function (res) {
|
||||
return res.json();
|
||||
});
|
||||
}).then(function (result) {
|
||||
if (fxInscrRedirectIfSessionExpired(result)) {
|
||||
return;
|
||||
}
|
||||
if (result && result.state === 'ok' && result.number) {
|
||||
self.applyNumber(String(result.number));
|
||||
return;
|
||||
}
|
||||
if (result && result.message === 'ocr_not_configured') {
|
||||
self.setStatus(
|
||||
(document.documentElement.lang === 'en')
|
||||
? 'OCR not configured (Vision API key missing).'
|
||||
: 'OCR non configuré (clé Vision manquante).',
|
||||
false
|
||||
);
|
||||
} else {
|
||||
var base = scanMsg(self.panel, 'data-msg-invalid', 'Numéro illisible. Cadrez un seul numéro.', 'Unreadable number. Frame a single number.');
|
||||
if (result && result.seen) {
|
||||
base += ' (' + result.seen + ')';
|
||||
}
|
||||
self.setStatus(base, false);
|
||||
}
|
||||
window.setTimeout(function () {
|
||||
if (activeScan === self) {
|
||||
self.showReady();
|
||||
}
|
||||
}, 2200);
|
||||
}).catch(function () {
|
||||
self.setStatus(scanMsg(self.panel, 'data-msg-invalid', 'Numéro illisible. Cadrez un seul numéro.', 'Unreadable number. Frame a single number.'), false);
|
||||
window.setTimeout(function () {
|
||||
if (activeScan === self) {
|
||||
self.showReady();
|
||||
}
|
||||
}, 2200);
|
||||
}).then(function () {
|
||||
self.ocrBusy = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
grabOcrCanvas(self).then(function (canvas) {
|
||||
if (!canvas) {
|
||||
throw new Error('capture');
|
||||
@ -939,14 +1002,16 @@
|
||||
},
|
||||
audio: false
|
||||
});
|
||||
var workerPromise = getGlobalOcrWorker();
|
||||
var readyPromise = cfg.bibOcrCloud
|
||||
? cameraPromise.then(function (stream) { return [stream, null]; })
|
||||
: Promise.all([cameraPromise, getGlobalOcrWorker()]);
|
||||
|
||||
Promise.all([cameraPromise, workerPromise]).then(function (results) {
|
||||
readyPromise.then(function (results) {
|
||||
if (activeScan !== self) {
|
||||
results[0].getTracks().forEach(function (track) { track.stop(); });
|
||||
return;
|
||||
}
|
||||
self.workerReady = true;
|
||||
self.workerReady = cfg.bibOcrCloud ? true : !!results[1];
|
||||
self.stream = results[0];
|
||||
self.video = document.createElement('video');
|
||||
self.video.setAttribute('playsinline', 'true');
|
||||
@ -970,7 +1035,7 @@
|
||||
});
|
||||
};
|
||||
|
||||
if (document.querySelector('.inscr-gestion-bib-scan-toggle')) {
|
||||
if (document.querySelector('.inscr-gestion-bib-scan-toggle') && !cfg.bibOcrCloud) {
|
||||
preloadOcrWorker();
|
||||
}
|
||||
|
||||
|
||||
@ -1467,6 +1467,131 @@ function fxInscrGestionRenderQrPanel($intEveId, $strLangue) {
|
||||
echo '</div></div></details>';
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4401 — OCR dossard via Google Cloud Vision (serveur).
|
||||
* Solution produit : ~95 % sur photos reelles vs ~50 % Tesseract.js mobile.
|
||||
*/
|
||||
function fxInscrGestionBibOcrCloudEnabled() {
|
||||
return defined('MSIN_BIB_OCR_VISION_API_KEY')
|
||||
&& trim((string)MSIN_BIB_OCR_VISION_API_KEY) !== '';
|
||||
}
|
||||
|
||||
/** Extraire un numero de dossard depuis du texte OCR (plus long groupe 1–6 chiffres). */
|
||||
function fxInscrGestionExtractBibNumberFromOcr($strRaw) {
|
||||
if (!preg_match_all('/\d+/', (string)$strRaw, $arrM) || empty($arrM[0])) {
|
||||
return '';
|
||||
}
|
||||
$arrMatches = $arrM[0];
|
||||
$blnAllSingles = true;
|
||||
foreach ($arrMatches as $strM) {
|
||||
if (strlen($strM) !== 1) {
|
||||
$blnAllSingles = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($blnAllSingles) {
|
||||
$strJoined = implode('', $arrMatches);
|
||||
return (strlen($strJoined) >= 1 && strlen($strJoined) <= 6) ? $strJoined : '';
|
||||
}
|
||||
$strBest = '';
|
||||
foreach ($arrMatches as $strM) {
|
||||
$intLen = strlen($strM);
|
||||
if ($intLen <= 6 && $intLen > strlen($strBest)) {
|
||||
$strBest = $strM;
|
||||
}
|
||||
}
|
||||
return $strBest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appel REST Vision TEXT_DETECTION (sans SDK Composer).
|
||||
* $strImageBase64 = contenu image (jpeg/png), sans prefixe data:.
|
||||
* @return array{ok:bool, text?:string, number?:string, message?:string}
|
||||
*/
|
||||
function fxInscrGestionVisionReadBibNumber($strImageBase64) {
|
||||
$strKey = defined('MSIN_BIB_OCR_VISION_API_KEY')
|
||||
? trim((string)MSIN_BIB_OCR_VISION_API_KEY)
|
||||
: '';
|
||||
if ($strKey === '') {
|
||||
return array('ok' => false, 'message' => 'ocr_not_configured');
|
||||
}
|
||||
|
||||
$strImageBase64 = preg_replace('#^data:image/[^;]+;base64,#', '', (string)$strImageBase64);
|
||||
$strImageBase64 = preg_replace('/\s+/', '', $strImageBase64);
|
||||
if ($strImageBase64 === '' || strlen($strImageBase64) > 5 * 1024 * 1024) {
|
||||
return array('ok' => false, 'message' => 'image_invalid');
|
||||
}
|
||||
|
||||
$strUrl = 'https://vision.googleapis.com/v1/images:annotate?key=' . rawurlencode($strKey);
|
||||
$arrPayload = array(
|
||||
'requests' => array(
|
||||
array(
|
||||
'image' => array('content' => $strImageBase64),
|
||||
'features' => array(
|
||||
array('type' => 'TEXT_DETECTION', 'maxResults' => 10),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
$strBody = json_encode($arrPayload);
|
||||
|
||||
$strResponse = '';
|
||||
$intHttp = 0;
|
||||
if (function_exists('curl_init')) {
|
||||
$ch = curl_init($strUrl);
|
||||
curl_setopt_array($ch, array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_HTTPHEADER => array('Content-Type: application/json'),
|
||||
CURLOPT_POSTFIELDS => $strBody,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 20,
|
||||
));
|
||||
$strResponse = (string)curl_exec($ch);
|
||||
$intHttp = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
} else {
|
||||
$ctx = stream_context_create(array(
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/json\r\n",
|
||||
'content' => $strBody,
|
||||
'timeout' => 20,
|
||||
),
|
||||
));
|
||||
$strResponse = (string)@file_get_contents($strUrl, false, $ctx);
|
||||
if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) {
|
||||
$intHttp = (int)$m[1];
|
||||
}
|
||||
}
|
||||
|
||||
if ($strResponse === '' || ($intHttp > 0 && $intHttp >= 400)) {
|
||||
error_log('[bib_ocr] Vision HTTP ' . $intHttp . ' body=' . substr($strResponse, 0, 300));
|
||||
return array('ok' => false, 'message' => 'vision_http');
|
||||
}
|
||||
|
||||
$arrJson = json_decode($strResponse, true);
|
||||
if (!is_array($arrJson)) {
|
||||
return array('ok' => false, 'message' => 'vision_parse');
|
||||
}
|
||||
if (!empty($arrJson['error']['message'])) {
|
||||
error_log('[bib_ocr] Vision error: ' . $arrJson['error']['message']);
|
||||
return array('ok' => false, 'message' => 'vision_api');
|
||||
}
|
||||
|
||||
$strText = '';
|
||||
if (!empty($arrJson['responses'][0]['textAnnotations'][0]['description'])) {
|
||||
$strText = (string)$arrJson['responses'][0]['textAnnotations'][0]['description'];
|
||||
} elseif (!empty($arrJson['responses'][0]['fullTextAnnotation']['text'])) {
|
||||
$strText = (string)$arrJson['responses'][0]['fullTextAnnotation']['text'];
|
||||
}
|
||||
|
||||
$strNum = fxInscrGestionExtractBibNumberFromOcr($strText);
|
||||
if ($strNum === '') {
|
||||
return array('ok' => false, 'message' => 'no_number', 'text' => $strText);
|
||||
}
|
||||
return array('ok' => true, 'number' => $strNum, 'text' => $strText);
|
||||
}
|
||||
|
||||
function fxInscrGestionRenderBibScanBlock($strBibInputId, $strLangue) {
|
||||
echo '<div class="inscr-gestion-bib-scan-panel" hidden';
|
||||
echo ' data-bib-input-id="' . fxInscrGestionEsc($strBibInputId) . '"';
|
||||
|
||||
@ -7,10 +7,17 @@
|
||||
* Constantes *
|
||||
*
|
||||
**************/
|
||||
define('_VERSION_CODE', '4.72.797');
|
||||
define('_VERSION_CODE', '4.72.798');
|
||||
define('_DATE_CODE', '2026-07-13');
|
||||
//MSIN-4290
|
||||
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');
|
||||
|
||||
// MSIN-4401 — OCR dossard : Google Cloud Vision (TEXT_DETECTION).
|
||||
// Sans cette cle, le scan cloud est desactive (pas de Tesseract hasardeux sur mobile).
|
||||
// Creer une cle API Vision (restreindre par IP / referer) et coller ici.
|
||||
if (!defined('MSIN_BIB_OCR_VISION_API_KEY')) {
|
||||
define('MSIN_BIB_OCR_VISION_API_KEY', '');
|
||||
}
|
||||
// Outil temporaire ms1_kc_set.php — modifier key_chain_http (lien /kc/{pk}/)
|
||||
// KEYCHAIN_HTTP_EDIT_SECRET : paramètre ?k= dans l'URL (mot de passe d'accès à la page)
|
||||
// KEYCHAIN_HTTP_EDIT_SHIFT : ajouté au numéro saisi (ex. 8 → pk 108 en base)
|
||||
|
||||
@ -42,6 +42,10 @@ if (!function_exists('fxV2RegisterScript')) {
|
||||
return [
|
||||
'domain' => $vDomaine,
|
||||
'lang' => $strLangue,
|
||||
// MSIN-4401 — true = OCR dossard via serveur (Vision), pas Tesseract local.
|
||||
'bibOcrCloud' => function_exists('fxInscrGestionBibOcrCloudEnabled')
|
||||
? (bool)fxInscrGestionBibOcrCloudEnabled()
|
||||
: false,
|
||||
'preloaderWait' => afficheTexte('preloader_wait', 0, 0, 1),
|
||||
'refundCancelHint' => ($strLangue === 'en')
|
||||
? 'Your transaction has been refunded. If you also want to cancel it in the registration system:'
|
||||
|
||||
Reference in New Issue
Block a user