更新
This commit is contained in:
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
require_once __DIR__ . '/ClinicalImageTextCache.php';
|
||||
|
||||
/** Optional local OCR evidence. It never replaces or decides the visual AI request. */
|
||||
class ClinicalImageTextService
|
||||
{
|
||||
@@ -21,7 +23,7 @@ class ClinicalImageTextService
|
||||
$indices = self::indices($images);
|
||||
if ($indices === null) return self::failure([], 'invalid_request');
|
||||
$fallback = self::failure($indices, 'unavailable');
|
||||
$handles = []; $paths = []; $directory = ''; $process = null; $closed = false;
|
||||
$handles = []; $paths = []; $directory = ''; $process = null; $closed = false; $reuse = null;
|
||||
try {
|
||||
self::reap();
|
||||
if ($shouldAbort !== null && $shouldAbort()) return self::failure($indices, 'cancelled');
|
||||
@@ -57,6 +59,16 @@ class ClinicalImageTextService
|
||||
$sources[] = ['index' => $image['index'], 'path' => $path];
|
||||
}
|
||||
if ($sources === []) return ['status' => 'completed', 'images' => []];
|
||||
$reuse = ClinicalImageTextCache::acquire($root, $sources, $config, $python, $script, $end, $shouldAbort);
|
||||
if (isset($config['_cache_scope']) && $shouldAbort !== null && $shouldAbort()) return self::failure($indices, 'cancelled');
|
||||
if ($reuse !== null) {
|
||||
$candidate = $reuse->cached($sources, $end, $shouldAbort);
|
||||
$cached = self::validateOutput($candidate, $indices, $textLimit);
|
||||
if ($shouldAbort !== null && $shouldAbort()) return self::failure($indices, 'cancelled');
|
||||
if (microtime(true) >= $end) return self::failure($indices, 'deadline');
|
||||
if ($cached !== null && array_column($candidate['images'], 'index') === $indices
|
||||
&& ($cached['status'] === 'completed' || ($cached['reason'] ?? '') === 'unreadable_sources')) return $cached;
|
||||
}
|
||||
if (microtime(true) >= $end) return self::failure($indices, 'deadline');
|
||||
$directory = rtrim(sys_get_temp_dir(), '/\\') . DIRECTORY_SEPARATOR . 'clinical-ocr-' . bin2hex(random_bytes(16));
|
||||
if (!@mkdir($directory, 0700)) return self::failure($indices, 'runtime_unavailable');
|
||||
@@ -111,10 +123,12 @@ class ClinicalImageTextService
|
||||
$validated['status'] = count(array_filter($validated['images'], static fn (array $row): bool => $row['status'] === 'processed')) > 0 ? 'partial' : 'unavailable';
|
||||
$validated['reason'] = $forced;
|
||||
}
|
||||
if ($forced === '' && self::cacheableRecognition($data, $indices)) $reuse?->remember($validated, $sources, $end, $shouldAbort);
|
||||
return $validated;
|
||||
} catch (\Throwable $error) {
|
||||
return $fallback;
|
||||
} finally {
|
||||
$reuse?->close();
|
||||
if (is_resource($process) && !$closed && !self::terminate($process)) self::$unreaped[] = $process;
|
||||
foreach ($handles as $handle) if (is_resource($handle)) fclose($handle);
|
||||
// Windows may defer inherited handle release briefly after termination.
|
||||
@@ -181,6 +195,23 @@ class ClinicalImageTextService
|
||||
}
|
||||
}
|
||||
|
||||
/** The worker's aggregate partial/unavailable status also covers a normal image with no text.
|
||||
* Check raw per-row terminal reasons before validation discards them; incomplete/error rows
|
||||
* must never become reusable just because the subprocess itself exited successfully.
|
||||
*/
|
||||
private static function cacheableRecognition(array $data, array $indices): bool
|
||||
{
|
||||
if (array_column($data['images'], 'index') !== $indices) return false;
|
||||
$processed = 0;
|
||||
foreach ($data['images'] as $row) {
|
||||
if ($row['status'] === 'processed' && !isset($row['reason'])) { $processed++; continue; }
|
||||
if ($row['status'] !== 'unreadable' || ($row['reason'] ?? '') !== 'no_text') return false;
|
||||
}
|
||||
$status = $processed === count($indices) ? 'completed' : ($processed > 0 ? 'partial' : 'unavailable');
|
||||
return $data['status'] === $status && ($status === 'completed'
|
||||
? !isset($data['reason']) : ($data['reason'] ?? '') === 'unreadable_sources');
|
||||
}
|
||||
|
||||
private static function validateOutput($data, array $indices, int $textLimit): ?array
|
||||
{
|
||||
if (!is_array($data) || !in_array($data['status'] ?? '', ['completed', 'partial', 'unavailable'], true)
|
||||
|
||||
Reference in New Issue
Block a user