From 4f90696cad8301b0b4a7b3084801dc40737d6b34 Mon Sep 17 00:00:00 2001 From: Moses Paul R Date: Wed, 30 Oct 2024 01:09:18 +0400 Subject: [PATCH 1/6] switch to threadpool executor and don't return heatmaps, affinity maps and segmentation maps where they're not needed. Clean up executor code --- benchmark/detection.py | 2 +- surya/detection.py | 24 ++++++++++++------------ surya/layout.py | 27 ++++++++++++++------------- surya/schema.py | 6 +++--- surya/util/parallel.py | 20 +++++++++++++++----- 5 files changed, 45 insertions(+), 34 deletions(-) diff --git a/benchmark/detection.py b/benchmark/detection.py index d149abb..65dc70e 100644 --- a/benchmark/detection.py +++ b/benchmark/detection.py @@ -55,7 +55,7 @@ def main(): correct_boxes.append([rescale_bbox(b, (1000, 1000), img_size) for b in boxes]) start = time.time() - predictions = batch_text_detection(images, model, processor) + predictions = batch_text_detection(images, model, processor, include_heatmap=False, include_affinity_map=False) surya_time = time.time() - start if args.tesseract: diff --git a/surya/detection.py b/surya/detection.py index eb97e18..8a9789a 100644 --- a/surya/detection.py +++ b/surya/detection.py @@ -13,10 +13,10 @@ from surya.schema import TextDetectionResult from surya.settings import settings from tqdm import tqdm -from concurrent.futures import ProcessPoolExecutor +from concurrent.futures import ThreadPoolExecutor import torch.nn.functional as F -from surya.util.parallel import FakeParallel +from surya.util.parallel import FakeExecutor def get_batch_size(): @@ -107,10 +107,13 @@ def batch_detection( yield preds, [orig_sizes[j] for j in batch_image_idxs] -def parallel_get_lines(preds, orig_sizes): +def parallel_get_lines(preds, orig_sizes, include_heatmap=True, include_affinity_map=True): heatmap, affinity_map = preds - heat_img = Image.fromarray((heatmap * 255).astype(np.uint8)) - aff_img = Image.fromarray((affinity_map * 255).astype(np.uint8)) + heat_img, aff_img = None, None + if include_heatmap: + heat_img = Image.fromarray((heatmap * 255).astype(np.uint8)) + if include_affinity_map: + aff_img = Image.fromarray((affinity_map * 255).astype(np.uint8)) affinity_size = list(reversed(affinity_map.shape)) heatmap_size = list(reversed(heatmap.shape)) bboxes = get_and_clean_boxes(heatmap, heatmap_size, orig_sizes) @@ -126,19 +129,16 @@ def parallel_get_lines(preds, orig_sizes): return result -def batch_text_detection(images: List, model, processor, batch_size=None) -> List[TextDetectionResult]: +def batch_text_detection(images: List, model, processor, batch_size=None, include_heatmap=True, include_affinity_map=True) -> List[TextDetectionResult]: detection_generator = batch_detection(images, model, processor, batch_size=batch_size) postprocessing_futures = [] max_workers = min(settings.DETECTOR_POSTPROCESSING_CPU_WORKERS, len(images)) parallelize = not settings.IN_STREAMLIT and len(images) >= settings.DETECTOR_MIN_PARALLEL_THRESH - - with ProcessPoolExecutor( - max_workers=max_workers, - ) if parallelize else contextlib.nullcontext() as executor: - func = executor.submit if parallelize else FakeParallel + executor = ThreadPoolExecutor if parallelize else FakeExecutor + with executor(max_workers=max_workers) as e: for preds, orig_sizes in detection_generator: for pred, orig_size in zip(preds, orig_sizes): - postprocessing_futures.append(func(parallel_get_lines, pred, orig_size)) + postprocessing_futures.append(e.submit(parallel_get_lines, pred, orig_size, include_heatmap, include_affinity_map)) return [future.result() for future in postprocessing_futures] diff --git a/surya/layout.py b/surya/layout.py index f1afd03..85175ee 100644 --- a/surya/layout.py +++ b/surya/layout.py @@ -1,6 +1,6 @@ import contextlib from collections import defaultdict -from concurrent.futures import ProcessPoolExecutor +from concurrent.futures import ThreadPoolExecutor from typing import List, Optional from PIL import Image import numpy as np @@ -9,7 +9,7 @@ from surya.postprocessing.heatmap import keep_largest_boxes, get_and_clean_boxes, get_detected_boxes from surya.schema import LayoutResult, LayoutBox, TextDetectionResult from surya.settings import settings -from surya.util.parallel import FakeParallel +from surya.util.parallel import FakeExecutor def get_regions_from_detection_result(detection_result: TextDetectionResult, heatmaps: List[np.ndarray], orig_size, id2label, segment_assignment, vertical_line_width=20) -> List[LayoutBox]: @@ -167,7 +167,7 @@ def get_regions(heatmaps: List[np.ndarray], orig_size, id2label, segment_assignm return bboxes -def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detection_results=None) -> LayoutResult: +def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detection_results=None, include_heatmaps=True, include_segmentation_map=True) -> LayoutResult: logits = np.stack(heatmaps, axis=0) segment_assignment = logits.argmax(axis=0) if detection_results is not None: @@ -176,39 +176,40 @@ def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detect else: bboxes = get_regions(heatmaps, orig_size, id2label, segment_assignment) - segmentation_img = Image.fromarray(segment_assignment.astype(np.uint8)) + segmentation_img = None + if include_segmentation_map: + segmentation_img = Image.fromarray(segment_assignment.astype(np.uint8)) result = LayoutResult( bboxes=bboxes, segmentation_map=segmentation_img, - heatmaps=heatmaps, + heatmaps=heatmaps if include_heatmaps else None, image_bbox=[0, 0, orig_size[0], orig_size[1]] ) return result -def batch_layout_detection(images: List, model, processor, detection_results: Optional[List[TextDetectionResult]] = None, batch_size=None) -> List[LayoutResult]: +def batch_layout_detection(images: List, model, processor, detection_results: Optional[List[TextDetectionResult]] = None, batch_size=None, include_heatmaps=True, include_segmentation_map=True) -> List[LayoutResult]: layout_generator = batch_detection(images, model, processor, batch_size=batch_size) id2label = model.config.id2label max_workers = min(settings.DETECTOR_POSTPROCESSING_CPU_WORKERS, len(images)) parallelize = not settings.IN_STREAMLIT and len(images) >= settings.DETECTOR_MIN_PARALLEL_THRESH postprocessing_futures = [] - with ProcessPoolExecutor( - max_workers=max_workers, - ) if parallelize else contextlib.nullcontext() as executor: + executor = ThreadPoolExecutor if parallelize else FakeExecutor + with executor(max_workers=max_workers) as e: img_idx = 0 - func = executor.submit if parallelize else FakeParallel - for preds, orig_sizes in layout_generator: for pred, orig_size in zip(preds, orig_sizes): - future = func( + future = e.submit( parallel_get_regions, pred, orig_size, id2label, - detection_results[img_idx] if detection_results else None + detection_results[img_idx] if detection_results else None, + include_heatmaps, + include_segmentation_map ) postprocessing_futures.append(future) diff --git a/surya/schema.py b/surya/schema.py index 1f41438..58c59f1 100644 --- a/surya/schema.py +++ b/surya/schema.py @@ -163,14 +163,14 @@ class OCRResult(BaseModel): class TextDetectionResult(BaseModel): bboxes: List[PolygonBox] vertical_lines: List[ColumnLine] - heatmap: Any - affinity_map: Any + heatmap: Optional[Any] + affinity_map: Optional[Any] image_bbox: List[float] class LayoutResult(BaseModel): bboxes: List[LayoutBox] - segmentation_map: Any + segmentation_map: Optional[Any] image_bbox: List[float] diff --git a/surya/util/parallel.py b/surya/util/parallel.py index 015c425..da01b27 100644 --- a/surya/util/parallel.py +++ b/surya/util/parallel.py @@ -1,6 +1,16 @@ -class FakeParallel(): - def __init__(self, func, *args): - self._result = func(*args) +from concurrent.futures import Future, Executor - def result(self): - return self._result +class FakeFuture(Future): + def __init__(self, func, *args, **kwargs): + super().__init__() + try: + self.set_result(func(*args, **kwargs)) + except Exception as e: + self.set_exception(e) + +class FakeExecutor(Executor): + def __init__(self, max_workers=None): + super().__init__() + + def submit(self, fn, *args, **kwargs): + return FakeFuture(fn, *args, **kwargs) From 4c594f4f50058cb5f599d4e601ae4e031d5bfeac Mon Sep 17 00:00:00 2001 From: Moses Paul R Date: Wed, 30 Oct 2024 15:01:07 +0400 Subject: [PATCH 2/6] switch to `include_maps` --- benchmark/detection.py | 2 +- surya/detection.py | 9 ++++----- surya/layout.py | 11 +++++------ surya/schema.py | 1 + 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/benchmark/detection.py b/benchmark/detection.py index 65dc70e..260cc0f 100644 --- a/benchmark/detection.py +++ b/benchmark/detection.py @@ -55,7 +55,7 @@ def main(): correct_boxes.append([rescale_bbox(b, (1000, 1000), img_size) for b in boxes]) start = time.time() - predictions = batch_text_detection(images, model, processor, include_heatmap=False, include_affinity_map=False) + predictions = batch_text_detection(images, model, processor, include_maps=False) surya_time = time.time() - start if args.tesseract: diff --git a/surya/detection.py b/surya/detection.py index 8a9789a..39bff6b 100644 --- a/surya/detection.py +++ b/surya/detection.py @@ -107,12 +107,11 @@ def batch_detection( yield preds, [orig_sizes[j] for j in batch_image_idxs] -def parallel_get_lines(preds, orig_sizes, include_heatmap=True, include_affinity_map=True): +def parallel_get_lines(preds, orig_sizes, include_maps=True): heatmap, affinity_map = preds heat_img, aff_img = None, None - if include_heatmap: + if include_maps: heat_img = Image.fromarray((heatmap * 255).astype(np.uint8)) - if include_affinity_map: aff_img = Image.fromarray((affinity_map * 255).astype(np.uint8)) affinity_size = list(reversed(affinity_map.shape)) heatmap_size = list(reversed(heatmap.shape)) @@ -129,7 +128,7 @@ def parallel_get_lines(preds, orig_sizes, include_heatmap=True, include_affinity return result -def batch_text_detection(images: List, model, processor, batch_size=None, include_heatmap=True, include_affinity_map=True) -> List[TextDetectionResult]: +def batch_text_detection(images: List, model, processor, batch_size=None, include_maps=True) -> List[TextDetectionResult]: detection_generator = batch_detection(images, model, processor, batch_size=batch_size) postprocessing_futures = [] @@ -139,6 +138,6 @@ def batch_text_detection(images: List, model, processor, batch_size=None, includ with executor(max_workers=max_workers) as e: for preds, orig_sizes in detection_generator: for pred, orig_size in zip(preds, orig_sizes): - postprocessing_futures.append(e.submit(parallel_get_lines, pred, orig_size, include_heatmap, include_affinity_map)) + postprocessing_futures.append(e.submit(parallel_get_lines, pred, orig_size, include_maps)) return [future.result() for future in postprocessing_futures] diff --git a/surya/layout.py b/surya/layout.py index 85175ee..b11afdd 100644 --- a/surya/layout.py +++ b/surya/layout.py @@ -167,7 +167,7 @@ def get_regions(heatmaps: List[np.ndarray], orig_size, id2label, segment_assignm return bboxes -def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detection_results=None, include_heatmaps=True, include_segmentation_map=True) -> LayoutResult: +def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detection_results=None, include_maps=True) -> LayoutResult: logits = np.stack(heatmaps, axis=0) segment_assignment = logits.argmax(axis=0) if detection_results is not None: @@ -177,20 +177,20 @@ def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detect bboxes = get_regions(heatmaps, orig_size, id2label, segment_assignment) segmentation_img = None - if include_segmentation_map: + if include_maps: segmentation_img = Image.fromarray(segment_assignment.astype(np.uint8)) result = LayoutResult( bboxes=bboxes, segmentation_map=segmentation_img, - heatmaps=heatmaps if include_heatmaps else None, + heatmaps=heatmaps if include_maps else None, image_bbox=[0, 0, orig_size[0], orig_size[1]] ) return result -def batch_layout_detection(images: List, model, processor, detection_results: Optional[List[TextDetectionResult]] = None, batch_size=None, include_heatmaps=True, include_segmentation_map=True) -> List[LayoutResult]: +def batch_layout_detection(images: List, model, processor, detection_results: Optional[List[TextDetectionResult]] = None, batch_size=None, include_maps=True) -> List[LayoutResult]: layout_generator = batch_detection(images, model, processor, batch_size=batch_size) id2label = model.config.id2label @@ -208,8 +208,7 @@ def batch_layout_detection(images: List, model, processor, detection_results: Op orig_size, id2label, detection_results[img_idx] if detection_results else None, - include_heatmaps, - include_segmentation_map + include_maps ) postprocessing_futures.append(future) diff --git a/surya/schema.py b/surya/schema.py index 58c59f1..a9fb57b 100644 --- a/surya/schema.py +++ b/surya/schema.py @@ -171,6 +171,7 @@ class TextDetectionResult(BaseModel): class LayoutResult(BaseModel): bboxes: List[LayoutBox] segmentation_map: Optional[Any] + heatmaps: Optional[Any] image_bbox: List[float] From cca6b730e43c0b999b22f889b9ea5b86f0a53629 Mon Sep 17 00:00:00 2001 From: Moses Paul R Date: Wed, 30 Oct 2024 15:21:05 +0400 Subject: [PATCH 3/6] simpler FakeFuture and FakeExecutor --- surya/util/parallel.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/surya/util/parallel.py b/surya/util/parallel.py index da01b27..2779c8e 100644 --- a/surya/util/parallel.py +++ b/surya/util/parallel.py @@ -1,16 +1,19 @@ -from concurrent.futures import Future, Executor - -class FakeFuture(Future): +class FakeFuture: def __init__(self, func, *args, **kwargs): - super().__init__() - try: - self.set_result(func(*args, **kwargs)) - except Exception as e: - self.set_exception(e) + self._result = func(*args, **kwargs) + + def result(self): + return self._result + +class FakeExecutor: + def __init__(self, **kwargs): + pass + + def __enter__(self): + return self -class FakeExecutor(Executor): - def __init__(self, max_workers=None): - super().__init__() + def __exit__(self, *excinfo): + pass def submit(self, fn, *args, **kwargs): return FakeFuture(fn, *args, **kwargs) From c37b4cf1101e17c30d5e8745eba0046382761903 Mon Sep 17 00:00:00 2001 From: Moses Paul R Date: Wed, 30 Oct 2024 15:35:56 +0400 Subject: [PATCH 4/6] include_maps=False by default --- benchmark/detection.py | 2 +- detect_layout.py | 2 +- detect_text.py | 2 +- surya/detection.py | 4 ++-- surya/layout.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/benchmark/detection.py b/benchmark/detection.py index 260cc0f..d149abb 100644 --- a/benchmark/detection.py +++ b/benchmark/detection.py @@ -55,7 +55,7 @@ def main(): correct_boxes.append([rescale_bbox(b, (1000, 1000), img_size) for b in boxes]) start = time.time() - predictions = batch_text_detection(images, model, processor, include_maps=False) + predictions = batch_text_detection(images, model, processor) surya_time = time.time() - start if args.tesseract: diff --git a/detect_layout.py b/detect_layout.py index 8fa98f2..b9ce718 100644 --- a/detect_layout.py +++ b/detect_layout.py @@ -39,7 +39,7 @@ def main(): start = time.time() line_predictions = batch_text_detection(images, det_model, det_processor) - layout_predictions = batch_layout_detection(images, model, processor, line_predictions) + layout_predictions = batch_layout_detection(images, model, processor, line_predictions, include_maps=args.debug) result_path = os.path.join(args.results_dir, folder_name) os.makedirs(result_path, exist_ok=True) if args.debug: diff --git a/detect_text.py b/detect_text.py index e7b0e5a..c7a6317 100644 --- a/detect_text.py +++ b/detect_text.py @@ -35,7 +35,7 @@ def main(): folder_name = os.path.basename(args.input_path).split(".")[0] start = time.time() - predictions = batch_text_detection(images, model, processor) + predictions = batch_text_detection(images, model, processor, include_maps=args.debug) result_path = os.path.join(args.results_dir, folder_name) os.makedirs(result_path, exist_ok=True) end = time.time() diff --git a/surya/detection.py b/surya/detection.py index 39bff6b..0915596 100644 --- a/surya/detection.py +++ b/surya/detection.py @@ -107,7 +107,7 @@ def batch_detection( yield preds, [orig_sizes[j] for j in batch_image_idxs] -def parallel_get_lines(preds, orig_sizes, include_maps=True): +def parallel_get_lines(preds, orig_sizes, include_maps=False): heatmap, affinity_map = preds heat_img, aff_img = None, None if include_maps: @@ -128,7 +128,7 @@ def parallel_get_lines(preds, orig_sizes, include_maps=True): return result -def batch_text_detection(images: List, model, processor, batch_size=None, include_maps=True) -> List[TextDetectionResult]: +def batch_text_detection(images: List, model, processor, batch_size=None, include_maps=False) -> List[TextDetectionResult]: detection_generator = batch_detection(images, model, processor, batch_size=batch_size) postprocessing_futures = [] diff --git a/surya/layout.py b/surya/layout.py index b11afdd..96adc5b 100644 --- a/surya/layout.py +++ b/surya/layout.py @@ -167,7 +167,7 @@ def get_regions(heatmaps: List[np.ndarray], orig_size, id2label, segment_assignm return bboxes -def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detection_results=None, include_maps=True) -> LayoutResult: +def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detection_results=None, include_maps=False) -> LayoutResult: logits = np.stack(heatmaps, axis=0) segment_assignment = logits.argmax(axis=0) if detection_results is not None: @@ -190,7 +190,7 @@ def parallel_get_regions(heatmaps: List[np.ndarray], orig_size, id2label, detect return result -def batch_layout_detection(images: List, model, processor, detection_results: Optional[List[TextDetectionResult]] = None, batch_size=None, include_maps=True) -> List[LayoutResult]: +def batch_layout_detection(images: List, model, processor, detection_results: Optional[List[TextDetectionResult]] = None, batch_size=None, include_maps=False) -> List[LayoutResult]: layout_generator = batch_detection(images, model, processor, batch_size=batch_size) id2label = model.config.id2label From 267e48989d433af7026dbcc767c2eb953eb2999e Mon Sep 17 00:00:00 2001 From: Moses Paul R Date: Wed, 30 Oct 2024 17:39:15 +0400 Subject: [PATCH 5/6] minor heatmap optimizations --- surya/postprocessing/heatmap.py | 39 ++++++++++++++------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/surya/postprocessing/heatmap.py b/surya/postprocessing/heatmap.py index be5d593..1c37551 100644 --- a/surya/postprocessing/heatmap.py +++ b/surya/postprocessing/heatmap.py @@ -105,8 +105,7 @@ def detect_boxes(linemap, text_threshold, low_text): ex, ey = min(img_w, x + w + niter + buffer), min(img_h, y + h + niter + buffer) mask = (labels[sy:ey, sx:ex] == k) - selected_linemap = linemap[sy:ey, sx:ex][mask] - line_max = np.max(selected_linemap) + line_max = np.max(linemap[sy:ey, sx:ex][mask]) # thresholding if line_max < text_threshold: @@ -115,13 +114,13 @@ def detect_boxes(linemap, text_threshold, low_text): segmap = mask.astype(np.uint8) ksize = buffer + niter - kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(ksize, ksize)) + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (ksize, ksize)) selected_segmap = cv2.dilate(segmap, kernel) # make box - indices = np.nonzero(selected_segmap) - x_inds = indices[1] + sx - y_inds = indices[0] + sy + y_inds, x_inds = np.nonzero(selected_segmap) + x_inds += sx + y_inds += sy np_contours = np.column_stack((x_inds, y_inds)) rectangle = cv2.minAreaRect(np_contours) box = cv2.boxPoints(rectangle) @@ -130,19 +129,17 @@ def detect_boxes(linemap, text_threshold, low_text): w, h = np.linalg.norm(box[0] - box[1]), np.linalg.norm(box[1] - box[2]) box_ratio = max(w, h) / (min(w, h) + 1e-5) if abs(1 - box_ratio) <= 0.1: - l, r = min(np_contours[:, 0]), max(np_contours[:, 0]) - t, b = min(np_contours[:, 1]), max(np_contours[:, 1]) + l, r = np_contours[:, 0].min(), np_contours[:, 0].max() + t, b = np_contours[:, 1].min(), np_contours[:, 1].max() box = np.array([[l, t], [r, t], [r, b], [l, b]], dtype=np.float32) # make clock-wise order startidx = box.sum(axis=1).argmin() - box = np.roll(box, 4-startidx, 0) - box = np.array(box) + box = np.roll(box, 4 - startidx, 0) - confidence = line_max max_confidence = max(max_confidence, line_max) - confidences.append(confidence) + confidences.append(line_max) det.append(box) if max_confidence > 0: @@ -150,19 +147,18 @@ def detect_boxes(linemap, text_threshold, low_text): return det, confidences -def get_detected_boxes(textmap, text_threshold=None, low_text=None) -> List[PolygonBox]: +def get_detected_boxes(textmap, text_threshold=None, low_text=None) -> List[PolygonBox]: if text_threshold is None: text_threshold = settings.DETECTOR_TEXT_THRESHOLD - if low_text is None: low_text = settings.DETECTOR_BLANK_THRESHOLD - textmap = textmap.copy() - textmap = textmap.astype(np.float32) + if textmap.dtype != np.float32: + textmap = textmap.astype(np.float32) + boxes, confidences = detect_boxes(textmap, text_threshold, low_text) # From point form to box form - boxes = [PolygonBox(polygon=box, confidence=confidence) for box, confidence in zip(boxes, confidences)] - return boxes + return [PolygonBox(polygon=box, confidence=confidence) for box, confidence in zip(boxes, confidences)] def get_and_clean_boxes(textmap, processor_size, image_size, text_threshold=None, low_text=None) -> List[PolygonBox]: @@ -175,8 +171,7 @@ def get_and_clean_boxes(textmap, processor_size, image_size, text_threshold=None return bboxes - -def draw_bboxes_on_image(bboxes, image, labels=None, label_font_size=10, color: str | list='red'): +def draw_bboxes_on_image(bboxes, image, labels=None, label_font_size=10, color: str | list = 'red'): polys = [] for bb in bboxes: # Clockwise polygon @@ -191,7 +186,7 @@ def draw_bboxes_on_image(bboxes, image, labels=None, label_font_size=10, color: return draw_polys_on_image(polys, image, labels, label_font_size=label_font_size, color=color) -def draw_polys_on_image(corners, image, labels=None, box_padding=-1, label_offset=1, label_font_size=10, color: str | list='red'): +def draw_polys_on_image(corners, image, labels=None, box_padding=-1, label_offset=1, label_font_size=10, color: str | list = 'red'): draw = ImageDraw.Draw(image) font_path = get_font_path() label_font = ImageFont.truetype(font_path, label_font_size) @@ -223,5 +218,3 @@ def draw_polys_on_image(corners, image, labels=None, box_padding=-1, label_offse ) return image - - From 015bc314f63d2e5139f393a4a9a7c15fb4632133 Mon Sep 17 00:00:00 2001 From: Moses Paul R Date: Wed, 30 Oct 2024 22:09:29 +0400 Subject: [PATCH 6/6] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1fb9ba8..bdd6d2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "surya-ocr" -version = "0.6.12" +version = "0.6.13" description = "OCR, layout, reading order, and table recognition in 90+ languages" authors = ["Vik Paruchuri "] readme = "README.md"