| """OpenVINO-based PDF page classifier for production inference.""" |
|
|
| import json |
| from pathlib import Path |
| from typing import Any |
|
|
| import numpy as np |
| import numpy.typing as npt |
|
|
| try: |
| from openvino import Core |
| from openvino import CompiledModel |
| except ImportError as _e: |
| raise ImportError( |
| "openvino is required for OpenVINO inference.\n" |
| "Install with: pip install openvino" |
| ) from _e |
|
|
| try: |
| from .base_classifier import _BasePDFPageClassifier |
| except ImportError: |
| from base_classifier import _BasePDFPageClassifier |
|
|
|
|
| class PDFPageClassifierOV(_BasePDFPageClassifier): |
| """Classify PDF pages using a deployed OpenVINO IR model. |
| |
| Loads a self-contained deployment directory produced by |
| ``export_onnx.save_for_deployment`` (with ``export_openvino=True``) and |
| exposes the same ``predict`` interface as ``PDFPageClassifier``. |
| |
| Automatically selects the INT8 variant (``model_ov_int8.xml``) when it |
| exists alongside the FP32 model, falling back to ``model_ov.xml``. |
| |
| Example:: |
| |
| clf = PDFPageClassifierOV.from_pretrained("outputs/run-42/deployment") |
| result = clf.predict("page_001.png") |
| print(result["needs_image_embedding"], result["predicted_classes"]) |
| """ |
|
|
| def __init__( |
| self, |
| model_path: str, |
| config: dict[str, Any], |
| device: str = "CPU", |
| ) -> None: |
| """Initialise the classifier. |
| |
| Args: |
| model_path: Path to the OpenVINO IR ``.xml`` file. |
| config: Deployment config dict (same schema as config.json written |
| by save_for_deployment). |
| device: OpenVINO device string (``"CPU"``, ``"GPU"``, ``"AUTO"``). |
| """ |
| super().__init__(config) |
| compiled: CompiledModel = Core().compile_model(model_path, device) |
| self._session: CompiledModel = compiled |
| self._input_name: str = compiled.input(0).get_any_name() |
| self._output = compiled.output(0) |
|
|
| @classmethod |
| def from_pretrained( |
| cls, |
| model_dir: str, |
| device: str = "CPU", |
| ) -> "PDFPageClassifierOV": |
| """Load a classifier from a deployment directory. |
| |
| The directory must contain: |
| - ``model_ov.xml`` / ``model_ov_int8.xml`` — exported by |
| save_for_deployment with ``export_openvino=True`` |
| - ``config.json`` — written by save_for_deployment |
| |
| The INT8 model (``model_ov_int8.xml``) is preferred when present. |
| |
| Args: |
| model_dir: Path to the deployment directory. |
| device: OpenVINO device string (``"CPU"``, ``"GPU"``, ``"AUTO"``). |
| |
| Returns: |
| Initialised PDFPageClassifierOV. |
| """ |
| path = Path(model_dir) |
| config_path = path / "config.json" |
|
|
| if not config_path.exists(): |
| raise FileNotFoundError(f"config.json not found in {model_dir}") |
|
|
| |
| candidates = [ |
| "openvino_model_int8.xml", |
| "openvino_model.xml", |
| "model_ov_int8.xml", |
| "model_ov.xml", |
| ] |
| for candidate in candidates: |
| if (path / candidate).exists(): |
| model_path = path / candidate |
| break |
| else: |
| raise FileNotFoundError( |
| f"No OpenVINO model found in {model_dir}. " |
| f"Expected one of: {', '.join(candidates)}. " |
| "Export with save_for_deployment(..., export_openvino=True)." |
| ) |
|
|
| with open(config_path, encoding="utf-8") as f: |
| config = json.load(f) |
|
|
| return cls(str(model_path), config, device=device) |
|
|
| def _run_batch(self, batch_input: "npt.NDArray[np.float32]") -> "npt.NDArray[np.float32]": |
| return self._session({self._input_name: batch_input})[self._output] |
|
|