client = $client; return; } $apiConfig = new Config([ 'accessKeyId' => $config['access_key_id'], 'accessKeySecret' => $config['access_key_secret'], 'endpoint' => $config['endpoint'], ]); $this->client = new Ocrapi($apiConfig); } public function recognize(string $imagePath, array $options = []): array { try { // Check if file exists if (!file_exists($imagePath)) { throw new \Exception("Image file not found: {$imagePath}"); } // Get cutType from options (default: "question") $cutType = $options['cutType'] ?? 'question'; $subject = $options['subject'] ?? 'Math'; // Read file content $fileStream = fopen($imagePath, 'rb'); $stream = \GuzzleHttp\Psr7\Utils::streamFor($fileStream); $request = new \AlibabaCloud\SDK\Ocrapi\V20210707\Models\RecognizeEduPaperCutRequest([ 'body' => $stream, 'cutType' => $cutType, 'imageType' => 'photo', 'subject' => $subject, 'outputOricoord' => false ]); $runtime = new RuntimeOptions([]); // Call Aliyun API $response = $this->client->recognizeEduPaperCutWithOptions($request, $runtime); // Close stream if (is_resource($fileStream)) { fclose($fileStream); } // Parse response $body = json_decode(json_encode($response->body), true); // Detailed logging Log::info('Aliyun EduPaperCut Full Response', [ 'cutType' => $cutType, 'has_data' => isset($body['data']), 'request_id' => $body['requestId'] ?? null, 'code' => $body['code'] ?? null, 'message' => $body['message'] ?? null, 'body_keys' => array_keys($body ?? []) ]); // Log raw data if exists if (isset($body['data'])) { $dataPreview = is_string($body['data']) ? substr($body['data'], 0, 500) : json_encode($body['data']); Log::info('Aliyun Data Preview', ['data' => $dataPreview]); } // Extract data from Aliyun response $questions = []; if (isset($body['data'])) { // The data field is a JSON string $data = is_string($body['data']) ? json_decode($body['data'], true) : $body['data']; // Extract page_list -> subject_list OR answer_list if (isset($data['page_list']) && is_array($data['page_list'])) { foreach ($data['page_list'] as $page) { // Determine which list to use based on cutType $itemList = null; if ($cutType === 'answer' && isset($page['answer_list'])) { $itemList = $page['answer_list']; } elseif (isset($page['subject_list'])) { $itemList = $page['subject_list']; } if ($itemList && is_array($itemList)) { foreach ($itemList as $item) { // Extract question/answer data $questionNumber = null; if (isset($item['ids']) && is_array($item['ids']) && !empty($item['ids'])) { $questionNumber = $item['ids'][0]; } else { $questionNumber = count($questions) + 1; } // Get text - if not provided, build from prism_wordsInfo $text = $item['text'] ?? ''; if (empty($text) && isset($item['prism_wordsInfo']) && is_array($item['prism_wordsInfo'])) { $words = []; foreach ($item['prism_wordsInfo'] as $wordInfo) { if (isset($wordInfo['word'])) { $words[] = $wordInfo['word']; } } $text = implode('', $words); } // Calculate confidence from prism_wordsInfo $confidence = 0.0; if (isset($item['prism_wordsInfo']) && is_array($item['prism_wordsInfo'])) { $totalProb = 0; $count = 0; foreach ($item['prism_wordsInfo'] as $wordInfo) { if (isset($wordInfo['prob'])) { $totalProb += $wordInfo['prob']; $count++; } } $confidence = $count > 0 ? ($totalProb / $count) / 100 : 0.0; } $questions[] = [ 'question_number' => $questionNumber, 'content' => $text, 'cut_type' => $cutType, 'confidence' => $confidence, 'raw_data' => $item ]; } } } } } return [ 'raw' => $body, 'questions' => $questions, 'cut_type' => $cutType ]; } catch (\Exception $e) { Log::error('Aliyun OCR Error', [ 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); throw $e; } } }