Edit on GitHub

leaf_focus.ocr.keras_ocr

OCR using keras-ocr.

  1"""OCR using keras-ocr."""
  2
  3from __future__ import annotations
  4
  5import logging
  6import os
  7import pathlib
  8
  9import numpy as np
 10
 11from beartype import beartype, typing
 12
 13from leaf_focus import utils
 14from leaf_focus.ocr import model
 15
 16
 17logger = logging.getLogger(__name__)
 18
 19
 20@beartype
 21class OpticalCharacterRecognition:
 22    """OCR implementation using keras-ocr."""
 23
 24    def __init__(self) -> None:
 25        """Create a new OpticalCharacterRecognition."""
 26        self._pipeline = None
 27
 28    def engine_create(self) -> None:
 29        """Create the OCR engine.
 30
 31        Returns:
 32            None
 33        """
 34        if self._pipeline is not None:
 35            return
 36
 37        logger.warning("Creating keras ocr processing engine.")
 38
 39        log_level = logger.getEffectiveLevel()
 40
 41        # set TF_CPP_MIN_LOG_LEVEL before importing tensorflow
 42        # this allows changing the logging printed by tensorflow
 43        tf_log_level_map = {
 44            logging.DEBUG: "0",
 45            logging.INFO: "1",
 46            logging.WARNING: "2",
 47            logging.ERROR: "3",
 48        }
 49        os.environ["TF_CPP_MIN_LOG_LEVEL"] = tf_log_level_map.get(log_level, "1")
 50
 51        import tensorflow as tf
 52
 53        # also set the tf logger level
 54
 55        tf.get_logger().setLevel(log_level)
 56
 57        # check the CPU / GPU in use
 58        gpus = tf.config.list_physical_devices("GPU")
 59        logger.info("GPUs in use: '%s'.", gpus)
 60
 61        import keras_ocr
 62
 63        # TODO: allow specifying path to weights files for detector
 64        # detector_weights_path = ""
 65        # detector = keras_ocr.detection.Detector(weights=None)
 66        # detector.model = keras_ocr.detection.build_keras_model(
 67        #     weights_path=detector_weights_path, backbone_name="vgg"
 68        # )
 69        # detector.model.compile(loss="mse", optimizer="adam")
 70        detector = None
 71
 72        # TODO: allow specifying path to weights files for recogniser
 73        # recognizer_weights_path = ""
 74        # recognizer = keras_ocr.recognition.Recognizer(
 75        #     alphabet=keras_ocr.recognition.DEFAULT_ALPHABET, weights=None
 76        # )
 77        # recognizer.model.load_weights(recognizer_weights_path)
 78        recognizer = None
 79
 80        # see: https://github.com/faustomorales/keras-ocr
 81        # keras-ocr will automatically download pretrained
 82        # weights for the detector and recognizer.
 83        self._pipeline = keras_ocr.pipeline.Pipeline(
 84            detector=detector,
 85            recognizer=recognizer,
 86        )
 87
 88    def engine_run(
 89        self,
 90        image_file: pathlib.Path,
 91    ) -> tuple[list[typing.Any], typing.Any]:
 92        """Run the recognition engine.
 93
 94        Args:
 95            image_file: The path to the image file.
 96
 97        Returns:
 98            typing.Tuple[typing.List, typing.Any]: The list of images
 99                and list of recognition results.
100        """
101        import keras_ocr
102
103        self.engine_create()
104
105        if not self._pipeline:
106            msg = "Keras OCR pipeline has not been initialised yet."
107            logger.error(msg)
108            raise utils.LeafFocusError(msg)
109
110        images = [keras_ocr.tools.read(str(image_file))]
111        return images, self._pipeline.recognize(images)
112
113    def engine_annotate(  # type: ignore [no-untyped-def]
114        self,
115        image: np.ndarray | None,  # type: ignore [type-arg]
116        predictions: list[tuple[typing.Any, typing.Any]],
117        axis,  # noqa: ANN001
118    ) -> None:
119        """Run the annotation engine.
120
121        Args:
122            image: The image data.
123            predictions: The recognised text from the image.
124            axis: The plot axis for drawing annotations.
125
126        Returns:
127            None
128        """
129        import keras_ocr
130
131        keras_ocr.tools.drawAnnotations(image=image, predictions=predictions, ax=axis)
132
133    def recognise_text(
134        self,
135        image_file: pathlib.Path,
136        output_dir: pathlib.Path,
137    ) -> model.KerasOcrResult:
138        """Recognise text in an image file.
139
140        Args:
141            image_file: The path to the image file.
142            output_dir: The directory to write the results.
143
144        Returns:
145            model.KerasOcrResult: The text recognition results.
146        """
147        if not image_file:
148            msg = "Must supply image file."
149            raise utils.LeafFocusError(msg)
150        if not output_dir:
151            msg = "Must supply output directory."
152            raise utils.LeafFocusError(msg)
153        if not image_file.exists():
154            msg = f"Image file does not exist '{image_file}'."
155            raise utils.LeafFocusError(msg) from FileNotFoundError(image_file)
156
157        # check if output files already exist
158        annotations_file = utils.output_root(image_file, "annotations", output_dir)
159        annotations_file = annotations_file.with_suffix(".png")
160
161        predictions_file = utils.output_root(image_file, "predictions", output_dir)
162        predictions_file = predictions_file.with_suffix(".csv")
163
164        result = model.KerasOcrResult(
165            output_dir=output_dir,
166            annotations_file=annotations_file,
167            predictions_file=predictions_file,
168            items=[],
169        )
170
171        if annotations_file.exists() and predictions_file.exists():
172            logger.debug(
173                "Predictions and annotations files already exist for '%s'.",
174                image_file.stem,
175            )
176            all_items = list(model.TextItem.load(predictions_file))
177            result.items = model.TextItem.order_text_lines(all_items)
178            return result
179
180        # read in the image
181        logger.debug(
182            "Creating predictions and annotations files for '%s'.",
183            image_file.stem,
184        )
185
186        # Each list of predictions in prediction_groups is a list of
187        # (word, box) tuples.
188        images, prediction_groups = self.engine_run(image_file)
189
190        # Plot and save the predictions
191        for image, predictions in zip(images, prediction_groups, strict=False):
192            self.save_figure(annotations_file, image, predictions)
193
194            items = self.convert_predictions(predictions)
195            self.save_items(predictions_file, [item for line in items for item in line])
196            result.items = items
197
198        return result
199
200    def save_figure(
201        self,
202        annotation_file: pathlib.Path,
203        image: np.ndarray | None,  # type: ignore [type-arg]
204        predictions: list[tuple[typing.Any, typing.Any]],
205    ) -> None:
206        """Save the annotated image.
207
208        Args:
209            annotation_file: The path to the file containing annotations.
210            image: The image data.
211            predictions: The text recognition results.
212
213        Returns:
214            None
215        """
216        if not annotation_file:
217            msg = "Must supply annotation file."
218            raise utils.LeafFocusError(msg)
219
220        expected_image_shape = 3
221        if image is None or image.size < 1 or len(image.shape) != expected_image_shape:
222            msg_image = image.shape if image is not None else None
223            msg = f"Must supply valid image data, not '{msg_image}'."
224            raise utils.LeafFocusError(msg)
225        if not predictions:
226            predictions = []
227
228        logger.info("Saving OCR image to '%s'.", annotation_file)
229
230        import matplotlib as mpl
231
232        from matplotlib import pyplot as plt
233
234        mpl.use("agg")
235
236        annotation_file.parent.mkdir(exist_ok=True, parents=True)
237
238        fig, axis = plt.subplots(figsize=(20, 20))
239
240        self.engine_annotate(image, predictions, axis)
241
242        fig.savefig(str(annotation_file))
243        plt.close(fig)
244
245    def convert_predictions(
246        self,
247        predictions: list[tuple[typing.Any, typing.Any]],
248    ) -> list[list[model.TextItem]]:
249        """Convert predictions to items.
250
251        Args:
252            predictions: The list of recognised text.
253
254        Returns:
255            typing.List[typing.List[model.TextItem]]: The equivalent text items.
256        """
257        if not predictions:
258            predictions = []
259
260        items = [
261            model.TextItem.from_prediction(prediction) for prediction in predictions
262        ]
263
264        # order_text_lines sets the line number and line order
265        line_items = model.TextItem.order_text_lines(items)
266
267        return line_items
268
269    def save_items(
270        self,
271        items_file: pathlib.Path,
272        items: typing.Iterable[model.TextItem],
273    ) -> None:
274        """Save items to csv file.
275
276        Args:
277            items_file: Write the text items to this file.
278            items: The text items to save.
279
280        Returns:
281            None
282        """
283        if not items_file:
284            msg = "Must supply predictions file."
285            raise utils.LeafFocusError(msg)
286        if not items:
287            msg = "Must supply predictions data."
288            raise utils.LeafFocusError(msg)
289
290        logger.info("Saving OCR predictions to '%s'.", items_file)
291
292        items_list = list(items)
293        model.TextItem.save(items_file, items_list)
294
295    def _build_name(self, prefix: str, middle: str, suffix: str) -> str:
296        """Build the file name.
297
298        Args:
299            prefix: The text to add at the start.
300            middle: The text to add in the middle.
301            suffix: The text to add at the end.
302
303        Returns:
304            str: The built name.
305        """
306        prefix = prefix.strip("-")
307        middle = middle.strip("-")
308        suffix = suffix if suffix.startswith(".") else "." + suffix
309        return f"{prefix}-{middle}" + suffix
logger = <Logger leaf_focus.ocr.keras_ocr (WARNING)>
@beartype
class OpticalCharacterRecognition:
 21@beartype
 22class OpticalCharacterRecognition:
 23    """OCR implementation using keras-ocr."""
 24
 25    def __init__(self) -> None:
 26        """Create a new OpticalCharacterRecognition."""
 27        self._pipeline = None
 28
 29    def engine_create(self) -> None:
 30        """Create the OCR engine.
 31
 32        Returns:
 33            None
 34        """
 35        if self._pipeline is not None:
 36            return
 37
 38        logger.warning("Creating keras ocr processing engine.")
 39
 40        log_level = logger.getEffectiveLevel()
 41
 42        # set TF_CPP_MIN_LOG_LEVEL before importing tensorflow
 43        # this allows changing the logging printed by tensorflow
 44        tf_log_level_map = {
 45            logging.DEBUG: "0",
 46            logging.INFO: "1",
 47            logging.WARNING: "2",
 48            logging.ERROR: "3",
 49        }
 50        os.environ["TF_CPP_MIN_LOG_LEVEL"] = tf_log_level_map.get(log_level, "1")
 51
 52        import tensorflow as tf
 53
 54        # also set the tf logger level
 55
 56        tf.get_logger().setLevel(log_level)
 57
 58        # check the CPU / GPU in use
 59        gpus = tf.config.list_physical_devices("GPU")
 60        logger.info("GPUs in use: '%s'.", gpus)
 61
 62        import keras_ocr
 63
 64        # TODO: allow specifying path to weights files for detector
 65        # detector_weights_path = ""
 66        # detector = keras_ocr.detection.Detector(weights=None)
 67        # detector.model = keras_ocr.detection.build_keras_model(
 68        #     weights_path=detector_weights_path, backbone_name="vgg"
 69        # )
 70        # detector.model.compile(loss="mse", optimizer="adam")
 71        detector = None
 72
 73        # TODO: allow specifying path to weights files for recogniser
 74        # recognizer_weights_path = ""
 75        # recognizer = keras_ocr.recognition.Recognizer(
 76        #     alphabet=keras_ocr.recognition.DEFAULT_ALPHABET, weights=None
 77        # )
 78        # recognizer.model.load_weights(recognizer_weights_path)
 79        recognizer = None
 80
 81        # see: https://github.com/faustomorales/keras-ocr
 82        # keras-ocr will automatically download pretrained
 83        # weights for the detector and recognizer.
 84        self._pipeline = keras_ocr.pipeline.Pipeline(
 85            detector=detector,
 86            recognizer=recognizer,
 87        )
 88
 89    def engine_run(
 90        self,
 91        image_file: pathlib.Path,
 92    ) -> tuple[list[typing.Any], typing.Any]:
 93        """Run the recognition engine.
 94
 95        Args:
 96            image_file: The path to the image file.
 97
 98        Returns:
 99            typing.Tuple[typing.List, typing.Any]: The list of images
100                and list of recognition results.
101        """
102        import keras_ocr
103
104        self.engine_create()
105
106        if not self._pipeline:
107            msg = "Keras OCR pipeline has not been initialised yet."
108            logger.error(msg)
109            raise utils.LeafFocusError(msg)
110
111        images = [keras_ocr.tools.read(str(image_file))]
112        return images, self._pipeline.recognize(images)
113
114    def engine_annotate(  # type: ignore [no-untyped-def]
115        self,
116        image: np.ndarray | None,  # type: ignore [type-arg]
117        predictions: list[tuple[typing.Any, typing.Any]],
118        axis,  # noqa: ANN001
119    ) -> None:
120        """Run the annotation engine.
121
122        Args:
123            image: The image data.
124            predictions: The recognised text from the image.
125            axis: The plot axis for drawing annotations.
126
127        Returns:
128            None
129        """
130        import keras_ocr
131
132        keras_ocr.tools.drawAnnotations(image=image, predictions=predictions, ax=axis)
133
134    def recognise_text(
135        self,
136        image_file: pathlib.Path,
137        output_dir: pathlib.Path,
138    ) -> model.KerasOcrResult:
139        """Recognise text in an image file.
140
141        Args:
142            image_file: The path to the image file.
143            output_dir: The directory to write the results.
144
145        Returns:
146            model.KerasOcrResult: The text recognition results.
147        """
148        if not image_file:
149            msg = "Must supply image file."
150            raise utils.LeafFocusError(msg)
151        if not output_dir:
152            msg = "Must supply output directory."
153            raise utils.LeafFocusError(msg)
154        if not image_file.exists():
155            msg = f"Image file does not exist '{image_file}'."
156            raise utils.LeafFocusError(msg) from FileNotFoundError(image_file)
157
158        # check if output files already exist
159        annotations_file = utils.output_root(image_file, "annotations", output_dir)
160        annotations_file = annotations_file.with_suffix(".png")
161
162        predictions_file = utils.output_root(image_file, "predictions", output_dir)
163        predictions_file = predictions_file.with_suffix(".csv")
164
165        result = model.KerasOcrResult(
166            output_dir=output_dir,
167            annotations_file=annotations_file,
168            predictions_file=predictions_file,
169            items=[],
170        )
171
172        if annotations_file.exists() and predictions_file.exists():
173            logger.debug(
174                "Predictions and annotations files already exist for '%s'.",
175                image_file.stem,
176            )
177            all_items = list(model.TextItem.load(predictions_file))
178            result.items = model.TextItem.order_text_lines(all_items)
179            return result
180
181        # read in the image
182        logger.debug(
183            "Creating predictions and annotations files for '%s'.",
184            image_file.stem,
185        )
186
187        # Each list of predictions in prediction_groups is a list of
188        # (word, box) tuples.
189        images, prediction_groups = self.engine_run(image_file)
190
191        # Plot and save the predictions
192        for image, predictions in zip(images, prediction_groups, strict=False):
193            self.save_figure(annotations_file, image, predictions)
194
195            items = self.convert_predictions(predictions)
196            self.save_items(predictions_file, [item for line in items for item in line])
197            result.items = items
198
199        return result
200
201    def save_figure(
202        self,
203        annotation_file: pathlib.Path,
204        image: np.ndarray | None,  # type: ignore [type-arg]
205        predictions: list[tuple[typing.Any, typing.Any]],
206    ) -> None:
207        """Save the annotated image.
208
209        Args:
210            annotation_file: The path to the file containing annotations.
211            image: The image data.
212            predictions: The text recognition results.
213
214        Returns:
215            None
216        """
217        if not annotation_file:
218            msg = "Must supply annotation file."
219            raise utils.LeafFocusError(msg)
220
221        expected_image_shape = 3
222        if image is None or image.size < 1 or len(image.shape) != expected_image_shape:
223            msg_image = image.shape if image is not None else None
224            msg = f"Must supply valid image data, not '{msg_image}'."
225            raise utils.LeafFocusError(msg)
226        if not predictions:
227            predictions = []
228
229        logger.info("Saving OCR image to '%s'.", annotation_file)
230
231        import matplotlib as mpl
232
233        from matplotlib import pyplot as plt
234
235        mpl.use("agg")
236
237        annotation_file.parent.mkdir(exist_ok=True, parents=True)
238
239        fig, axis = plt.subplots(figsize=(20, 20))
240
241        self.engine_annotate(image, predictions, axis)
242
243        fig.savefig(str(annotation_file))
244        plt.close(fig)
245
246    def convert_predictions(
247        self,
248        predictions: list[tuple[typing.Any, typing.Any]],
249    ) -> list[list[model.TextItem]]:
250        """Convert predictions to items.
251
252        Args:
253            predictions: The list of recognised text.
254
255        Returns:
256            typing.List[typing.List[model.TextItem]]: The equivalent text items.
257        """
258        if not predictions:
259            predictions = []
260
261        items = [
262            model.TextItem.from_prediction(prediction) for prediction in predictions
263        ]
264
265        # order_text_lines sets the line number and line order
266        line_items = model.TextItem.order_text_lines(items)
267
268        return line_items
269
270    def save_items(
271        self,
272        items_file: pathlib.Path,
273        items: typing.Iterable[model.TextItem],
274    ) -> None:
275        """Save items to csv file.
276
277        Args:
278            items_file: Write the text items to this file.
279            items: The text items to save.
280
281        Returns:
282            None
283        """
284        if not items_file:
285            msg = "Must supply predictions file."
286            raise utils.LeafFocusError(msg)
287        if not items:
288            msg = "Must supply predictions data."
289            raise utils.LeafFocusError(msg)
290
291        logger.info("Saving OCR predictions to '%s'.", items_file)
292
293        items_list = list(items)
294        model.TextItem.save(items_file, items_list)
295
296    def _build_name(self, prefix: str, middle: str, suffix: str) -> str:
297        """Build the file name.
298
299        Args:
300            prefix: The text to add at the start.
301            middle: The text to add in the middle.
302            suffix: The text to add at the end.
303
304        Returns:
305            str: The built name.
306        """
307        prefix = prefix.strip("-")
308        middle = middle.strip("-")
309        suffix = suffix if suffix.startswith(".") else "." + suffix
310        return f"{prefix}-{middle}" + suffix

OCR implementation using keras-ocr.

OpticalCharacterRecognition()
25    def __init__(self) -> None:
26        """Create a new OpticalCharacterRecognition."""
27        self._pipeline = None

Create a new OpticalCharacterRecognition.

def engine_create(self) -> None:
29    def engine_create(self) -> None:
30        """Create the OCR engine.
31
32        Returns:
33            None
34        """
35        if self._pipeline is not None:
36            return
37
38        logger.warning("Creating keras ocr processing engine.")
39
40        log_level = logger.getEffectiveLevel()
41
42        # set TF_CPP_MIN_LOG_LEVEL before importing tensorflow
43        # this allows changing the logging printed by tensorflow
44        tf_log_level_map = {
45            logging.DEBUG: "0",
46            logging.INFO: "1",
47            logging.WARNING: "2",
48            logging.ERROR: "3",
49        }
50        os.environ["TF_CPP_MIN_LOG_LEVEL"] = tf_log_level_map.get(log_level, "1")
51
52        import tensorflow as tf
53
54        # also set the tf logger level
55
56        tf.get_logger().setLevel(log_level)
57
58        # check the CPU / GPU in use
59        gpus = tf.config.list_physical_devices("GPU")
60        logger.info("GPUs in use: '%s'.", gpus)
61
62        import keras_ocr
63
64        # TODO: allow specifying path to weights files for detector
65        # detector_weights_path = ""
66        # detector = keras_ocr.detection.Detector(weights=None)
67        # detector.model = keras_ocr.detection.build_keras_model(
68        #     weights_path=detector_weights_path, backbone_name="vgg"
69        # )
70        # detector.model.compile(loss="mse", optimizer="adam")
71        detector = None
72
73        # TODO: allow specifying path to weights files for recogniser
74        # recognizer_weights_path = ""
75        # recognizer = keras_ocr.recognition.Recognizer(
76        #     alphabet=keras_ocr.recognition.DEFAULT_ALPHABET, weights=None
77        # )
78        # recognizer.model.load_weights(recognizer_weights_path)
79        recognizer = None
80
81        # see: https://github.com/faustomorales/keras-ocr
82        # keras-ocr will automatically download pretrained
83        # weights for the detector and recognizer.
84        self._pipeline = keras_ocr.pipeline.Pipeline(
85            detector=detector,
86            recognizer=recognizer,
87        )

Create the OCR engine.

Returns:

None

def engine_run(self, image_file: pathlib.Path) -> tuple[list[typing.Any], typing.Any]:
 89    def engine_run(
 90        self,
 91        image_file: pathlib.Path,
 92    ) -> tuple[list[typing.Any], typing.Any]:
 93        """Run the recognition engine.
 94
 95        Args:
 96            image_file: The path to the image file.
 97
 98        Returns:
 99            typing.Tuple[typing.List, typing.Any]: The list of images
100                and list of recognition results.
101        """
102        import keras_ocr
103
104        self.engine_create()
105
106        if not self._pipeline:
107            msg = "Keras OCR pipeline has not been initialised yet."
108            logger.error(msg)
109            raise utils.LeafFocusError(msg)
110
111        images = [keras_ocr.tools.read(str(image_file))]
112        return images, self._pipeline.recognize(images)

Run the recognition engine.

Arguments:
  • image_file: The path to the image file.
Returns:

typing.Tuple[typing.List, typing.Any]: The list of images and list of recognition results.

def engine_annotate( self, image: numpy.ndarray | None, predictions: list[tuple[typing.Any, typing.Any]], axis) -> None:
114    def engine_annotate(  # type: ignore [no-untyped-def]
115        self,
116        image: np.ndarray | None,  # type: ignore [type-arg]
117        predictions: list[tuple[typing.Any, typing.Any]],
118        axis,  # noqa: ANN001
119    ) -> None:
120        """Run the annotation engine.
121
122        Args:
123            image: The image data.
124            predictions: The recognised text from the image.
125            axis: The plot axis for drawing annotations.
126
127        Returns:
128            None
129        """
130        import keras_ocr
131
132        keras_ocr.tools.drawAnnotations(image=image, predictions=predictions, ax=axis)

Run the annotation engine.

Arguments:
  • image: The image data.
  • predictions: The recognised text from the image.
  • axis: The plot axis for drawing annotations.
Returns:

None

def recognise_text( self, image_file: pathlib.Path, output_dir: pathlib.Path) -> leaf_focus.ocr.model.KerasOcrResult:
134    def recognise_text(
135        self,
136        image_file: pathlib.Path,
137        output_dir: pathlib.Path,
138    ) -> model.KerasOcrResult:
139        """Recognise text in an image file.
140
141        Args:
142            image_file: The path to the image file.
143            output_dir: The directory to write the results.
144
145        Returns:
146            model.KerasOcrResult: The text recognition results.
147        """
148        if not image_file:
149            msg = "Must supply image file."
150            raise utils.LeafFocusError(msg)
151        if not output_dir:
152            msg = "Must supply output directory."
153            raise utils.LeafFocusError(msg)
154        if not image_file.exists():
155            msg = f"Image file does not exist '{image_file}'."
156            raise utils.LeafFocusError(msg) from FileNotFoundError(image_file)
157
158        # check if output files already exist
159        annotations_file = utils.output_root(image_file, "annotations", output_dir)
160        annotations_file = annotations_file.with_suffix(".png")
161
162        predictions_file = utils.output_root(image_file, "predictions", output_dir)
163        predictions_file = predictions_file.with_suffix(".csv")
164
165        result = model.KerasOcrResult(
166            output_dir=output_dir,
167            annotations_file=annotations_file,
168            predictions_file=predictions_file,
169            items=[],
170        )
171
172        if annotations_file.exists() and predictions_file.exists():
173            logger.debug(
174                "Predictions and annotations files already exist for '%s'.",
175                image_file.stem,
176            )
177            all_items = list(model.TextItem.load(predictions_file))
178            result.items = model.TextItem.order_text_lines(all_items)
179            return result
180
181        # read in the image
182        logger.debug(
183            "Creating predictions and annotations files for '%s'.",
184            image_file.stem,
185        )
186
187        # Each list of predictions in prediction_groups is a list of
188        # (word, box) tuples.
189        images, prediction_groups = self.engine_run(image_file)
190
191        # Plot and save the predictions
192        for image, predictions in zip(images, prediction_groups, strict=False):
193            self.save_figure(annotations_file, image, predictions)
194
195            items = self.convert_predictions(predictions)
196            self.save_items(predictions_file, [item for line in items for item in line])
197            result.items = items
198
199        return result

Recognise text in an image file.

Arguments:
  • image_file: The path to the image file.
  • output_dir: The directory to write the results.
Returns:

model.KerasOcrResult: The text recognition results.

def save_figure( self, annotation_file: pathlib.Path, image: numpy.ndarray | None, predictions: list[tuple[typing.Any, typing.Any]]) -> None:
201    def save_figure(
202        self,
203        annotation_file: pathlib.Path,
204        image: np.ndarray | None,  # type: ignore [type-arg]
205        predictions: list[tuple[typing.Any, typing.Any]],
206    ) -> None:
207        """Save the annotated image.
208
209        Args:
210            annotation_file: The path to the file containing annotations.
211            image: The image data.
212            predictions: The text recognition results.
213
214        Returns:
215            None
216        """
217        if not annotation_file:
218            msg = "Must supply annotation file."
219            raise utils.LeafFocusError(msg)
220
221        expected_image_shape = 3
222        if image is None or image.size < 1 or len(image.shape) != expected_image_shape:
223            msg_image = image.shape if image is not None else None
224            msg = f"Must supply valid image data, not '{msg_image}'."
225            raise utils.LeafFocusError(msg)
226        if not predictions:
227            predictions = []
228
229        logger.info("Saving OCR image to '%s'.", annotation_file)
230
231        import matplotlib as mpl
232
233        from matplotlib import pyplot as plt
234
235        mpl.use("agg")
236
237        annotation_file.parent.mkdir(exist_ok=True, parents=True)
238
239        fig, axis = plt.subplots(figsize=(20, 20))
240
241        self.engine_annotate(image, predictions, axis)
242
243        fig.savefig(str(annotation_file))
244        plt.close(fig)

Save the annotated image.

Arguments:
  • annotation_file: The path to the file containing annotations.
  • image: The image data.
  • predictions: The text recognition results.
Returns:

None

def convert_predictions( self, predictions: list[tuple[typing.Any, typing.Any]]) -> list[list[leaf_focus.ocr.model.TextItem]]:
246    def convert_predictions(
247        self,
248        predictions: list[tuple[typing.Any, typing.Any]],
249    ) -> list[list[model.TextItem]]:
250        """Convert predictions to items.
251
252        Args:
253            predictions: The list of recognised text.
254
255        Returns:
256            typing.List[typing.List[model.TextItem]]: The equivalent text items.
257        """
258        if not predictions:
259            predictions = []
260
261        items = [
262            model.TextItem.from_prediction(prediction) for prediction in predictions
263        ]
264
265        # order_text_lines sets the line number and line order
266        line_items = model.TextItem.order_text_lines(items)
267
268        return line_items

Convert predictions to items.

Arguments:
  • predictions: The list of recognised text.
Returns:

typing.List[typing.List[model.TextItem]]: The equivalent text items.

def save_items( self, items_file: pathlib.Path, items: Iterable[leaf_focus.ocr.model.TextItem]) -> None:
270    def save_items(
271        self,
272        items_file: pathlib.Path,
273        items: typing.Iterable[model.TextItem],
274    ) -> None:
275        """Save items to csv file.
276
277        Args:
278            items_file: Write the text items to this file.
279            items: The text items to save.
280
281        Returns:
282            None
283        """
284        if not items_file:
285            msg = "Must supply predictions file."
286            raise utils.LeafFocusError(msg)
287        if not items:
288            msg = "Must supply predictions data."
289            raise utils.LeafFocusError(msg)
290
291        logger.info("Saving OCR predictions to '%s'.", items_file)
292
293        items_list = list(items)
294        model.TextItem.save(items_file, items_list)

Save items to csv file.

Arguments:
  • items_file: Write the text items to this file.
  • items: The text items to save.
Returns:

None