CloudVisionAPIのPHPクライアントライブラリでOCR処理の状態を非同期で取得したい


# 前提

- PDFファイルのドキュメントをデータ化するPHPアプリケーションを作りたい
- Google Cloud Vision APIの公式PHPクライアントライブラリをcomposerでインストール
 - google/cloud-vision
 - google/cloud-storage(PDFファイルや変換後のjsonデータはCloud Strageに保存されます)

# 状況
Cloud Vision APIでOCRを使いたいシーンでは、多くの方がGithub上の公式サンプルコードを参考にすると思います。

https://github.com/GoogleCloudPlatform/php-docs-samples/blob/master/vision/src/detect_pdf_gcs.php

上記コードではasyncBatchAnnotateFilesメソッドでOCRを実行しています。
解析結果はGoogle Coud Strageの指定パス以下にjsonファイルとして出力されます。

OCR処理自体は非同期で実行されますが、このサンプルではpollUntilCompleteメソッドを用いて処理終了を待っています。
素直にWebアプリケーションに組み込んだ場合は、OCRが完了するまでページの読み込み待ちという挙動になってしまいます。
解析するPDFファイルに認識対象（文字、画像、用紙サイズ、ページ数など）が多い場合、かなり時間がかかります。
呼び出し側のタイムアウトを伸ばすといった方法では対処できないシーンも発生すると思います（発生しました）

# Cloud Vision APIの完了を非同期で待つ

asyncBatchAnnotateImagesメソッドのドキュメントに以下の記述がありました。

https://github.com/googleapis/google-cloud-php-vision/blob/master/src/V1/Gapic/ImageAnnotatorGapicClient.php

```php:ImageAnnotatorGapicClient.php
    /*
     *     // Alternatively:
     *
     *     // start the operation, keep the operation name, and resume later
     *     $operationResponse = $imageAnnotatorClient->asyncBatchAnnotateImages($requests, $outputConfig);
     *     $operationName = $operationResponse->getName();
     *     // ... do other work
     *     $newOperationResponse = $imageAnnotatorClient->resumeOperation($operationName, 'asyncBatchAnnotateImages');
     *     while (!$newOperationResponse->isDone()) {
     *         // ... do other work
     *         $newOperationResponse->reload();
     *     }
     *     if ($newOperationResponse->operationSucceeded()) {
     *       $result = $newOperationResponse->getResult();
     *       // doSomethingWith($result)
     *     } else {
     *       $error = $newOperationResponse->getError();
     *       // handleError($error)
     *     }
     */
```

asyncBatchAnnotateImagesメソッドから戻るOperationResponseオブジェクトのgetNameメソッドで識別子を取得。
後からresumeOperationメソッドにその値を渡してやれば、対象のOperationResponseオブジェクトが取得できreloadで状態の更新もできるようです。

動作させてみたところ、$operationNameは

```
projects/my-project-name/operations/xxxxxxxxxxxxxxxx
```

といった書式のstringでした。
DB等に保存して後から読み込む事もできそうです。

#問題点

しかし、このソースコードをそのまま流用してもresumeOperationメソッドの実行で例外が発生してしまいました。

```
Google\Protobuf\Internal\GPBDecodeException: Error occurred during parsing: Class google.cloud.vision.v1.OperationMetadata hasn't been added to descriptor pool in /var/www/html/my-project/vendor/google/protobuf/src/Google/Protobuf/Internal/Message.php:1327
```

#解決

類似事例を検索してみると以下のページがヒットしました。

https://stackoverflow.com/questions/56898519/class-google-cloud-speech-v1-longrunningrecognizemetadata-hasnt-been-added-to-d

参考ページはSpeechClientの問題となっていますが、ImageAnnotatorに読み替えてinitOnceの呼び出しを追加します。

```php
ImageAnnotator::initOnce();
$newOperationResponse = $imageAnnotatorClient->resumeOperation($operationName, 'asyncBatchAnnotateImages');
```

これで正常にOperationResponseを取得できました。
isDone()とoperationSucceeded（）で正常終了が判断できたら後続の処理へ進むという流れで良さそうです。

今回は普通のウェブページでしたので、ajaxを使って定期的にステータスを確認し完了後に次ページにへ遷移してデータを表示するという作り方になりました。

#参考
VisionAPIはOCRだけではなく顔認識をはじめとした多種多様な画像解析API群です。
非常に多機能なため、初見では全体像の把握が難しい印象です。
OCR用途に絞りたい場合、下記サイトの解説がコンパクトにまとまっていて助かりました。

[Vision API OCR事始め(1)：TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの違い](https://technodaifuku.blogspot.com/2020/09/vision-api-ocrtextdetectiondocumenttext.html)