docreader-ocr 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/.gitignore +9 -1
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/PKG-INFO +2 -2
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/pyproject.toml +2 -2
- docreader_ocr-0.2.0/src/docreader/__init__.py +65 -0
- docreader_ocr-0.2.0/src/docreader/classifier/__init__.py +4 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/classifier/base.py +16 -2
- docreader_ocr-0.2.0/src/docreader/classifier/yolo_classifier.py +108 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/config.py +14 -11
- docreader_ocr-0.2.0/src/docreader/detector/__init__.py +4 -0
- docreader_ocr-0.2.0/src/docreader/detector/yolo_obb.py +97 -0
- docreader_ocr-0.2.0/src/docreader/factory.py +129 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/hub.py +53 -5
- docreader_ocr-0.2.0/src/docreader/ocr/__init__.py +4 -0
- docreader_ocr-0.2.0/src/docreader/ocr/easyocr_engine.py +91 -0
- docreader_ocr-0.2.0/src/docreader/pipeline.py +248 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/schemas.py +32 -0
- docreader_ocr-0.2.0/tests/test_pipeline.py +29 -0
- docreader_ocr-0.1.2/src/docreader/__init__.py +0 -17
- docreader_ocr-0.1.2/src/docreader/classifier/__init__.py +0 -4
- docreader_ocr-0.1.2/src/docreader/classifier/mobilenet.py +0 -80
- docreader_ocr-0.1.2/src/docreader/detector/__init__.py +0 -4
- docreader_ocr-0.1.2/src/docreader/detector/yolo_obb.py +0 -57
- docreader_ocr-0.1.2/src/docreader/ocr/__init__.py +0 -4
- docreader_ocr-0.1.2/src/docreader/ocr/easyocr_engine.py +0 -68
- docreader_ocr-0.1.2/src/docreader/pipeline.py +0 -242
- docreader_ocr-0.1.2/tests/IMG_20211108_155730.jpg +0 -0
- docreader_ocr-0.1.2/tests/test_pipeline.py +0 -19
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/.github/workflows/publish.yaml +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/LICENSE +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/README.md +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/detector/base.py +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/ocr/base.py +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/preprocessing/__init__.py +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/preprocessing/geometry.py +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/utils.py +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/tests/test_hub.py +0 -0
- {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/tests/test_run.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docreader-ocr
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Document OCR pipeline: classify → detect fields → recognize text
|
|
5
5
|
Project-URL: Homepage, https://github.com/mishanyacorleone/docreader
|
|
6
6
|
Project-URL: Repository, https://github.com/mishanyacorleone/docreader
|
|
7
7
|
Project-URL: Issues, https://github.com/mishanyacorleone/docreader/issues
|
|
8
|
-
Author-email: Mikhail Kardash <mishutqac@mail.
|
|
8
|
+
Author-email: Mikhail Kardash <mishutqac@mail.ru>, Ruslan Abzelilov <ruslanr26@mail.ru>, Ekaterina Karmanova <monitor81@mail.ru>
|
|
9
9
|
License: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: document,ocr,recognition,yolo
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "docreader-ocr"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Document OCR pipeline: classify → detect fields → recognize text"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
11
11
|
requires-python = ">=3.9"
|
|
12
12
|
authors = [
|
|
13
|
-
{name = "Mikhail Kardash", email = "mishutqac@mail.
|
|
13
|
+
{name = "Mikhail Kardash", email = "mishutqac@mail.ru"},
|
|
14
14
|
{name = "Ruslan Abzelilov", email = "ruslanr26@mail.ru"},
|
|
15
15
|
{name = "Ekaterina Karmanova", email = "monitor81@mail.ru"}
|
|
16
16
|
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
docreader — библиотека для распознавания текста с документов.
|
|
3
|
+
|
|
4
|
+
Быстрый старт:
|
|
5
|
+
from docreader import DocReader
|
|
6
|
+
reader = DocReader()
|
|
7
|
+
result = reader.process("photo.jpg")
|
|
8
|
+
for doc in result.documents:
|
|
9
|
+
print(doc.doc_type, doc.fields)
|
|
10
|
+
|
|
11
|
+
Standalone-компоненты (с автозагрузкой весов):
|
|
12
|
+
from docreader import create_classifier, create_detector, create_ocr
|
|
13
|
+
|
|
14
|
+
clf = create_classifier()
|
|
15
|
+
det = create_detector()
|
|
16
|
+
ocr = create_ocr()
|
|
17
|
+
|
|
18
|
+
Standalone-компоненты (со своими весами):
|
|
19
|
+
from docreader import DocClassifier, ZoneDetector, TextRecognizer
|
|
20
|
+
|
|
21
|
+
clf = DocClassifier(weights_path="/my/model.pt")
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from docreader.pipeline import DocReader
|
|
25
|
+
from docreader.schemas import DocumentResult, ZoneResult, PageResult
|
|
26
|
+
from docreader.config import PipelineConfig
|
|
27
|
+
|
|
28
|
+
# Классы компонентов (для кастомных весов)
|
|
29
|
+
from docreader.classifier import DocClassifier, BaseClassifier, ClassifiedDocument
|
|
30
|
+
from docreader.detector import ZoneDetector, BaseDetector, Detection
|
|
31
|
+
from docreader.ocr import TextRecognizer, BaseOcrEngine, OcrResult
|
|
32
|
+
|
|
33
|
+
# Фабрики (со стандартными весами)
|
|
34
|
+
from docreader.factory import create_classifier, create_detector, create_ocr
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Пайплайн
|
|
38
|
+
"DocReader",
|
|
39
|
+
"PipelineConfig",
|
|
40
|
+
|
|
41
|
+
# Результаты
|
|
42
|
+
"PageResult",
|
|
43
|
+
"DocumentResult",
|
|
44
|
+
"ZoneResult",
|
|
45
|
+
|
|
46
|
+
# Фабрики (стандартные веса)
|
|
47
|
+
"create_classifier",
|
|
48
|
+
"create_detector",
|
|
49
|
+
"create_ocr",
|
|
50
|
+
|
|
51
|
+
# Классы компонентов (кастомные веса)
|
|
52
|
+
"DocClassifier",
|
|
53
|
+
"ZoneDetector",
|
|
54
|
+
"TextRecognizer",
|
|
55
|
+
|
|
56
|
+
# Базовые классы (для наследования)
|
|
57
|
+
"BaseClassifier",
|
|
58
|
+
"ClassifiedDocument",
|
|
59
|
+
"BaseDetector",
|
|
60
|
+
"Detection",
|
|
61
|
+
"BaseOcrEngine",
|
|
62
|
+
"OcrResult",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
__version__ = "0.2.0"
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
"""Абстрактный интерфейс классификатора документов."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
4
7
|
import numpy as np
|
|
5
8
|
|
|
6
9
|
|
|
10
|
+
@dataclass
|
|
11
|
+
class ClassifiedDocument:
|
|
12
|
+
"""
|
|
13
|
+
Один найденный документ на изображении
|
|
14
|
+
"""
|
|
15
|
+
doc_type: str
|
|
16
|
+
confidence: float
|
|
17
|
+
crop: np.ndarray # вырезанный и выпрямленный документ (BGR)
|
|
18
|
+
obb_points: np.ndarray # shape (4, 2) - координаты в исходном состоянии
|
|
19
|
+
|
|
20
|
+
|
|
7
21
|
class BaseClassifier(ABC):
|
|
8
22
|
"""
|
|
9
23
|
Интерфейс для классификатора типа документа.
|
|
@@ -13,12 +27,12 @@ class BaseClassifier(ABC):
|
|
|
13
27
|
"""
|
|
14
28
|
|
|
15
29
|
@abstractmethod
|
|
16
|
-
def
|
|
30
|
+
def classify(self, source: Union[str, np.ndarray]) -> list[str, float]:
|
|
17
31
|
"""
|
|
18
32
|
Классифицирует изображение документа.
|
|
19
33
|
|
|
20
34
|
Args:
|
|
21
|
-
image: BGR изображение (numpy array).
|
|
35
|
+
image: путь к файлу или BGR изображение (numpy array).
|
|
22
36
|
|
|
23
37
|
Returns:
|
|
24
38
|
Кортеж (метка_класса, уверенность).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Классификатор документов на основе YOLO OBB."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from ultralytics import YOLO
|
|
8
|
+
|
|
9
|
+
from docreader.classifier.base import BaseClassifier, ClassifiedDocument
|
|
10
|
+
from docreader.preprocessing.geometry import crop_obb_region
|
|
11
|
+
from docreader.utils import load_image
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DocClassifier(BaseClassifier):
|
|
17
|
+
"""
|
|
18
|
+
Классификатор документов через YOLO OBB.
|
|
19
|
+
|
|
20
|
+
Находит один или несколько документов на фотографии,
|
|
21
|
+
определяет тип каждого и вырезает кроп.
|
|
22
|
+
|
|
23
|
+
Примеры:
|
|
24
|
+
# Стандартные веса (путь передаётся из пайплайна или вручную)
|
|
25
|
+
clf = DocClassifier(weights_path="/path/to/doc_classifier.pt")
|
|
26
|
+
docs = clf.classify(image)
|
|
27
|
+
|
|
28
|
+
# Без параметров — используется через DocReader,
|
|
29
|
+
# который сам подставит путь из конфига
|
|
30
|
+
reader = DocReader()
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
weights_path: путь к файлу весов YOLO.
|
|
34
|
+
device: устройство ("cpu", "cuda").
|
|
35
|
+
confidence_threshold: минимальная уверенность детекции.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
weights_path: str,
|
|
41
|
+
device: str = "cpu",
|
|
42
|
+
confidence_threshold: float = 0.3,
|
|
43
|
+
):
|
|
44
|
+
self._confidence_threshold = confidence_threshold
|
|
45
|
+
self._device = device
|
|
46
|
+
self._model = YOLO(weights_path)
|
|
47
|
+
|
|
48
|
+
logger.info(
|
|
49
|
+
f"DocClassifier initialized: device={self._device}, "
|
|
50
|
+
f"weights={weights_path}, "
|
|
51
|
+
f"classes={list(self._model.names.values())}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def classify(self, source: Union[str, np.ndarray]) -> list[ClassifiedDocument]:
|
|
55
|
+
"""
|
|
56
|
+
Находит документы на изображении.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
image: BGR изображение.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Список ClassifiedDocument. Пустой, если документы не найдены.
|
|
63
|
+
"""
|
|
64
|
+
image = load_image(source)
|
|
65
|
+
|
|
66
|
+
results = self._model(image, device=self._device, verbose=False)
|
|
67
|
+
|
|
68
|
+
documents = []
|
|
69
|
+
|
|
70
|
+
if results[0].obb is None:
|
|
71
|
+
logger.info("No documents detected")
|
|
72
|
+
return documents
|
|
73
|
+
|
|
74
|
+
for det in results[0].obb:
|
|
75
|
+
confidence = float(det.conf.cpu())
|
|
76
|
+
if confidence < self._confidence_threshold:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
class_id = int(det.cls.cpu())
|
|
80
|
+
doc_type = self._model.names[class_id]
|
|
81
|
+
obb_points = det.xyxyxyxy.cpu().numpy().reshape(4, 2)
|
|
82
|
+
|
|
83
|
+
crop = crop_obb_region(image, obb_points)
|
|
84
|
+
if crop is None or crop.size == 0:
|
|
85
|
+
logger.warning(
|
|
86
|
+
f"Failed to crop document: type={doc_type}, "
|
|
87
|
+
f"conf={confidence:.3f}"
|
|
88
|
+
)
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
documents.append(ClassifiedDocument(
|
|
92
|
+
doc_type=doc_type,
|
|
93
|
+
confidence=confidence,
|
|
94
|
+
crop=crop,
|
|
95
|
+
obb_points=obb_points,
|
|
96
|
+
))
|
|
97
|
+
|
|
98
|
+
logger.info(
|
|
99
|
+
f"Found {len(documents)} document(s): "
|
|
100
|
+
f"{[d.doc_type for d in documents]}"
|
|
101
|
+
)
|
|
102
|
+
return documents
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def class_names(self) -> list[str]:
|
|
106
|
+
"""Список поддерживаемых типов документов."""
|
|
107
|
+
return list(self._model.names.values())
|
|
108
|
+
|
|
@@ -14,6 +14,9 @@ class PipelineConfig:
|
|
|
14
14
|
"""
|
|
15
15
|
device: str = "auto"
|
|
16
16
|
|
|
17
|
+
classifier_weights: str = "doc_classifier.pt"
|
|
18
|
+
classifier_confidence: float = 0.3
|
|
19
|
+
|
|
17
20
|
# Типы документов и пути к YOLO-моделям (относительно models_dir)
|
|
18
21
|
detector_weights: dict[str, str] = field(default_factory=lambda: {
|
|
19
22
|
"attestat": "attestat.pt",
|
|
@@ -21,26 +24,26 @@ class PipelineConfig:
|
|
|
21
24
|
"passport": "passport.pt",
|
|
22
25
|
"snils": "snils.pt",
|
|
23
26
|
})
|
|
24
|
-
|
|
25
|
-
# Путь к весам классификатора (относительно models_dir)
|
|
26
|
-
classification_weights: str = "best_doc_classifier.pth"
|
|
27
|
-
|
|
28
|
-
class_labels: list[str] = field(default_factory=lambda: [
|
|
29
|
-
"attestat", "diplom", "passport", "snils", 'other'
|
|
30
|
-
])
|
|
27
|
+
detector_confidence: float = 0.25
|
|
31
28
|
|
|
32
29
|
# EasyOCR
|
|
33
|
-
skip_ocr_zones: frozenset[str] = DEFAULT_SKIP_OCR_ZONES
|
|
34
30
|
ocr_lang: list[str] = field(default_factory=lambda: ["ru"])
|
|
31
|
+
ocr_model_archive: str = "easyocr_custom.tar.gz"
|
|
32
|
+
ocr_model_subdir: str = "model"
|
|
33
|
+
ocr_network_subdir: str = "user_network"
|
|
35
34
|
ocr_recog_network: str = "custom_example"
|
|
36
35
|
ocr_download_enabled: bool = False
|
|
36
|
+
skip_ocr_zones: frozenset[str] = DEFAULT_SKIP_OCR_ZONES
|
|
37
37
|
|
|
38
38
|
enable_deskew: bool = True # Выравнивание по линиям Хафа
|
|
39
|
-
|
|
40
39
|
return_crops: bool = True # Сохранять кропы зон в результат
|
|
41
40
|
|
|
42
41
|
def resolve_device(self) -> str:
|
|
43
42
|
if self.device != "auto":
|
|
44
43
|
return self.device
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
try:
|
|
45
|
+
import torch
|
|
46
|
+
return "cuda" if torch.cuda.is_available() else "cpu"
|
|
47
|
+
except ImportError:
|
|
48
|
+
return "cpu"
|
|
49
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Детектор зон документа на основе YOLO OBB."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from ultralytics import YOLO
|
|
7
|
+
|
|
8
|
+
from docreader.detector.base import BaseDetector, Detection
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ZoneDetector(BaseDetector):
|
|
14
|
+
"""
|
|
15
|
+
Детектор полей документа через YOLO OBB с ленивой загрузкой.
|
|
16
|
+
|
|
17
|
+
Примеры:
|
|
18
|
+
det = ZoneDetector(weights_map={
|
|
19
|
+
"passport": "/path/to/passport.pt",
|
|
20
|
+
"diplom": "/path/to/diplom.pt",
|
|
21
|
+
})
|
|
22
|
+
zones = det.detect(image, doc_type="passport")
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
weights_map: словарь {doc_type: полный_путь_к_весам}.
|
|
26
|
+
device: устройство ("cpu", "cuda").
|
|
27
|
+
confidence_threshold: минимальная уверенность.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
weights_map: dict[str, str],
|
|
33
|
+
device: str = "cpu",
|
|
34
|
+
confidence_threshold: float = 0.25,
|
|
35
|
+
):
|
|
36
|
+
self._weights_map = weights_map
|
|
37
|
+
self._device = device
|
|
38
|
+
self._confidence_threshold = confidence_threshold
|
|
39
|
+
self._loaded_models: dict[str, YOLO] = {}
|
|
40
|
+
|
|
41
|
+
logger.info(
|
|
42
|
+
f"ZoneDetector initialized: device={self._device}, "
|
|
43
|
+
f"doc_types={list(self._weights_map.keys())}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def _get_model(self, doc_type: str) -> YOLO:
|
|
47
|
+
"""Загружает модель при первом обращении."""
|
|
48
|
+
if doc_type not in self._loaded_models:
|
|
49
|
+
if doc_type not in self._weights_map:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"No weights for doc_type='{doc_type}'. "
|
|
52
|
+
f"Available: {list(self._weights_map.keys())}"
|
|
53
|
+
)
|
|
54
|
+
path = self._weights_map[doc_type]
|
|
55
|
+
logger.info(f"Loading YOLO model for '{doc_type}': {path}")
|
|
56
|
+
self._loaded_models[doc_type] = YOLO(path)
|
|
57
|
+
return self._loaded_models[doc_type]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def supported_doc_types(self) -> list[str]:
|
|
61
|
+
"""Список поддерживаемых типов документов."""
|
|
62
|
+
return list(self._weights_map.keys())
|
|
63
|
+
|
|
64
|
+
def detect(self, image: np.ndarray, doc_type: str) -> list[Detection]:
|
|
65
|
+
"""
|
|
66
|
+
Обнаруживает зоны на изображении документа.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
image: BGR изображение (кроп одного документа).
|
|
70
|
+
doc_type: тип документа.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Список Detection.
|
|
74
|
+
"""
|
|
75
|
+
model = self._get_model(doc_type)
|
|
76
|
+
results = model(image, device=self._device, verbose=False)
|
|
77
|
+
|
|
78
|
+
detections = []
|
|
79
|
+
if results[0].obb is None:
|
|
80
|
+
return detections
|
|
81
|
+
|
|
82
|
+
for det in results[0].obb:
|
|
83
|
+
confidence = float(det.conf.cpu())
|
|
84
|
+
if confidence < self._confidence_threshold:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
zone_id = int(det.cls.cpu())
|
|
88
|
+
zone_name = model.names[zone_id]
|
|
89
|
+
obb_points = det.xyxyxyxy.cpu().numpy().flatten()
|
|
90
|
+
|
|
91
|
+
detections.append(Detection(
|
|
92
|
+
zone_name=zone_name,
|
|
93
|
+
obb_points=obb_points,
|
|
94
|
+
confidence=confidence,
|
|
95
|
+
))
|
|
96
|
+
|
|
97
|
+
return detections
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Фабричные функции для создания компонентов со стандартными весами.
|
|
3
|
+
|
|
4
|
+
Использование:
|
|
5
|
+
from docreader import create_classifier, create_detector, create_ocr
|
|
6
|
+
|
|
7
|
+
clf = create_classifier()
|
|
8
|
+
clf = create_classifier(confidence_threshold=0.5) # Переопределение
|
|
9
|
+
|
|
10
|
+
det = create_detector()
|
|
11
|
+
det = create_detector(device="cuda")
|
|
12
|
+
|
|
13
|
+
ocr = create_ocr()
|
|
14
|
+
ocr = create_ocr(lang=["en", "ru"])
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from docreader.config import PipelineConfig
|
|
18
|
+
from docreader.hub import ensure_model
|
|
19
|
+
from docreader.classifier.yolo_classifier import DocClassifier
|
|
20
|
+
from docreader.detector.yolo_obb import ZoneDetector
|
|
21
|
+
from docreader.ocr.easyocr_engine import TextRecognizer
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_classifier(
|
|
25
|
+
config: PipelineConfig | None = None,
|
|
26
|
+
**kwargs,
|
|
27
|
+
) -> DocClassifier:
|
|
28
|
+
"""
|
|
29
|
+
Создаёт классификатор документов со стандартными весами.
|
|
30
|
+
|
|
31
|
+
Веса скачиваются автоматически при первом вызове.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config: конфигурация (если None — используется дефолтная).
|
|
35
|
+
**kwargs: переопределение параметров DocClassifier
|
|
36
|
+
(weights_path, device, confidence_threshold).
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Готовый к работе DocClassifier.
|
|
40
|
+
|
|
41
|
+
Примеры:
|
|
42
|
+
clf = create_classifier()
|
|
43
|
+
clf = create_classifier(confidence_threshold=0.5)
|
|
44
|
+
clf = create_classifier(device="cuda")
|
|
45
|
+
"""
|
|
46
|
+
cfg = config or PipelineConfig()
|
|
47
|
+
|
|
48
|
+
defaults = {
|
|
49
|
+
"weights_path": str(ensure_model(cfg.classifier_weights)),
|
|
50
|
+
"device": cfg.resolve_device(),
|
|
51
|
+
"confidence_threshold": cfg.classifier_confidence
|
|
52
|
+
}
|
|
53
|
+
defaults.update(kwargs)
|
|
54
|
+
return DocClassifier(**defaults)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def create_detector(
|
|
58
|
+
config: PipelineConfig | None = None,
|
|
59
|
+
**kwargs,
|
|
60
|
+
) -> ZoneDetector:
|
|
61
|
+
"""
|
|
62
|
+
Создаёт детектор зон документов со стандартными весами.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
config: конфигурация (если None — используется дефолтная).
|
|
66
|
+
**kwargs: переопределение параметров ZoneDetector
|
|
67
|
+
(weights_map, device, confidence_threshold).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Готовый к работе ZoneDetector.
|
|
71
|
+
|
|
72
|
+
Примеры:
|
|
73
|
+
det = create_detector()
|
|
74
|
+
det = create_detector(device="cuda")
|
|
75
|
+
det = create_detector(confidence_threshold=0.1)
|
|
76
|
+
"""
|
|
77
|
+
cfg = config or PipelineConfig()
|
|
78
|
+
|
|
79
|
+
weights_map = {
|
|
80
|
+
doc_type: str(ensure_model(filename))
|
|
81
|
+
for doc_type, filename in cfg.detector_weights.items()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
defaults = {
|
|
85
|
+
"weights_map": weights_map,
|
|
86
|
+
"device": cfg.resolve_device(),
|
|
87
|
+
"confidence_threshold": cfg.detector_confidence,
|
|
88
|
+
}
|
|
89
|
+
defaults.update(kwargs)
|
|
90
|
+
return ZoneDetector(**defaults)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def create_ocr(
|
|
94
|
+
config: PipelineConfig | None = None,
|
|
95
|
+
**kwargs,
|
|
96
|
+
) -> TextRecognizer:
|
|
97
|
+
"""
|
|
98
|
+
Создаёт OCR-движок со стандартными моделями.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
config: конфигурация (если None — используется дефолтная).
|
|
102
|
+
**kwargs: переопределение параметров TextRecognizer
|
|
103
|
+
(lang, gpu, model_storage_directory, и т.д.).
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Готовый к работе TextRecognizer.
|
|
107
|
+
|
|
108
|
+
Примеры:
|
|
109
|
+
ocr = create_ocr()
|
|
110
|
+
ocr = create_ocr(lang=["en", "ru"])
|
|
111
|
+
ocr = create_ocr(gpu=False)
|
|
112
|
+
"""
|
|
113
|
+
cfg = config or PipelineConfig()
|
|
114
|
+
easyocr_dir = ensure_model(cfg.ocr_model_archive)
|
|
115
|
+
|
|
116
|
+
defaults = {
|
|
117
|
+
"lang": cfg.ocr_lang,
|
|
118
|
+
"gpu": cfg.resolve_device != "cpu",
|
|
119
|
+
"model_storage_directory": str(
|
|
120
|
+
easyocr_dir / cfg.ocr_model_subdir
|
|
121
|
+
),
|
|
122
|
+
"user_network_directory": str(
|
|
123
|
+
easyocr_dir / cfg.ocr_network_subdir
|
|
124
|
+
),
|
|
125
|
+
"recog_network": cfg.ocr_recog_network,
|
|
126
|
+
"download_enabled": cfg.ocr_download_enabled,
|
|
127
|
+
}
|
|
128
|
+
defaults.update(kwargs)
|
|
129
|
+
return TextRecognizer(**defaults)
|
|
@@ -17,14 +17,18 @@ from tqdm import tqdm
|
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
|
+
_BASE_URL_CLASSIFIER = "https://github.com/mishanyacorleone/docreader/releases/download/v0.2.0"
|
|
20
21
|
_BASE_URL = "https://github.com/mishanyacorleone/docreader/releases/download/v0.1.0"
|
|
21
22
|
|
|
22
23
|
MODEL_REGISTRY: dict[str, dict] = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"
|
|
24
|
+
# === Классификатор документов (YOLO OBB) ===
|
|
25
|
+
"doc_classifier.pt": {
|
|
26
|
+
"url": f"{_BASE_URL_CLASSIFIER}/doc_classifier.pt",
|
|
27
|
+
"sha256": "b1af689fe58849474a6a5cf879458fcba6d017233ca1bd54b5d83098cd9387f5",
|
|
28
|
+
"size_mb": 5.49,
|
|
27
29
|
},
|
|
30
|
+
|
|
31
|
+
# === Детекторы зон ===
|
|
28
32
|
"passport.pt": {
|
|
29
33
|
"url": f"{_BASE_URL}/passport.pt",
|
|
30
34
|
"sha256": "bebe46bcd4270442c1e14e9b5a403c9f59212d92ed8181af1326f9f80bc0f0c0",
|
|
@@ -45,6 +49,8 @@ MODEL_REGISTRY: dict[str, dict] = {
|
|
|
45
49
|
"sha256": "84775a6ff1ababb3f8e31a8aa768717cf9d65d8b84df9c0cd48eb7bdaf680218",
|
|
46
50
|
"size_mb": 5.82,
|
|
47
51
|
},
|
|
52
|
+
|
|
53
|
+
# === EasyOCR ===
|
|
48
54
|
"easyocr_custom.tar.gz": {
|
|
49
55
|
"url": f"{_BASE_URL}/easyocr_custom.tar.gz",
|
|
50
56
|
"sha256": "832ce5a7f3a1086d81beb1c991347e3f545a425646bc87f3f576ae06fecd2420",
|
|
@@ -179,4 +185,46 @@ def ensure_all_models(cache_dir: Optional[Path] = None) -> Path:
|
|
|
179
185
|
cache = cache_dir or get_cache_dir()
|
|
180
186
|
for filename in MODEL_REGISTRY:
|
|
181
187
|
ensure_model(filename, cache)
|
|
182
|
-
return cache
|
|
188
|
+
return cache
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_model_paths() -> dict[str, Path]:
|
|
192
|
+
"""
|
|
193
|
+
Возвращает словарь {имя_модели: полный_путь}
|
|
194
|
+
для всех зарегистрированных моделей.
|
|
195
|
+
"""
|
|
196
|
+
cache = get_cache_dir()
|
|
197
|
+
paths = {}
|
|
198
|
+
|
|
199
|
+
for filename, meta in MODEL_REGISTRY.items():
|
|
200
|
+
if "extract_to" in meta:
|
|
201
|
+
paths[filename] = cache / meta["extract_to"]
|
|
202
|
+
else:
|
|
203
|
+
paths[filename] = cache / filename
|
|
204
|
+
|
|
205
|
+
return paths
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_model_status() -> dict[str, dict]:
|
|
209
|
+
"""
|
|
210
|
+
Показывает статус всех моделей: путь, скачана ли, размер.
|
|
211
|
+
"""
|
|
212
|
+
cache = get_cache_dir()
|
|
213
|
+
status = {}
|
|
214
|
+
|
|
215
|
+
for filename, meta in MODEL_REGISTRY.items():
|
|
216
|
+
if "extract_to" in meta:
|
|
217
|
+
path = cache / meta["extract_to"]
|
|
218
|
+
exists = path.exists() and any(path.iterdir())
|
|
219
|
+
else:
|
|
220
|
+
path = cache / filename
|
|
221
|
+
exists = path.exists()
|
|
222
|
+
|
|
223
|
+
status[filename] = {
|
|
224
|
+
"path": str(path),
|
|
225
|
+
"downloaded": exists,
|
|
226
|
+
"size_mb": meta.get("size_mb", "?"),
|
|
227
|
+
"url": meta["url"],
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return status
|