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.
Files changed (37) hide show
  1. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/.gitignore +9 -1
  2. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/PKG-INFO +2 -2
  3. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/pyproject.toml +2 -2
  4. docreader_ocr-0.2.0/src/docreader/__init__.py +65 -0
  5. docreader_ocr-0.2.0/src/docreader/classifier/__init__.py +4 -0
  6. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/classifier/base.py +16 -2
  7. docreader_ocr-0.2.0/src/docreader/classifier/yolo_classifier.py +108 -0
  8. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/config.py +14 -11
  9. docreader_ocr-0.2.0/src/docreader/detector/__init__.py +4 -0
  10. docreader_ocr-0.2.0/src/docreader/detector/yolo_obb.py +97 -0
  11. docreader_ocr-0.2.0/src/docreader/factory.py +129 -0
  12. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/hub.py +53 -5
  13. docreader_ocr-0.2.0/src/docreader/ocr/__init__.py +4 -0
  14. docreader_ocr-0.2.0/src/docreader/ocr/easyocr_engine.py +91 -0
  15. docreader_ocr-0.2.0/src/docreader/pipeline.py +248 -0
  16. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/schemas.py +32 -0
  17. docreader_ocr-0.2.0/tests/test_pipeline.py +29 -0
  18. docreader_ocr-0.1.2/src/docreader/__init__.py +0 -17
  19. docreader_ocr-0.1.2/src/docreader/classifier/__init__.py +0 -4
  20. docreader_ocr-0.1.2/src/docreader/classifier/mobilenet.py +0 -80
  21. docreader_ocr-0.1.2/src/docreader/detector/__init__.py +0 -4
  22. docreader_ocr-0.1.2/src/docreader/detector/yolo_obb.py +0 -57
  23. docreader_ocr-0.1.2/src/docreader/ocr/__init__.py +0 -4
  24. docreader_ocr-0.1.2/src/docreader/ocr/easyocr_engine.py +0 -68
  25. docreader_ocr-0.1.2/src/docreader/pipeline.py +0 -242
  26. docreader_ocr-0.1.2/tests/IMG_20211108_155730.jpg +0 -0
  27. docreader_ocr-0.1.2/tests/test_pipeline.py +0 -19
  28. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/.github/workflows/publish.yaml +0 -0
  29. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/LICENSE +0 -0
  30. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/README.md +0 -0
  31. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/detector/base.py +0 -0
  32. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/ocr/base.py +0 -0
  33. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/preprocessing/__init__.py +0 -0
  34. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/preprocessing/geometry.py +0 -0
  35. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/src/docreader/utils.py +0 -0
  36. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/tests/test_hub.py +0 -0
  37. {docreader_ocr-0.1.2 → docreader_ocr-0.2.0}/tests/test_run.py +0 -0
@@ -63,4 +63,12 @@ htmlcov/
63
63
  *.log
64
64
  .env
65
65
  .env.local
66
- output/
66
+ output/
67
+
68
+ # ══════════════════════════════════════════
69
+ # Tests
70
+ # ══════════════════════════════════════════
71
+
72
+ tests/*.jpg
73
+ tests/*.png
74
+ tests/*.jpeg
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docreader-ocr
3
- Version: 0.1.2
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.com>, Ruslan Abzelilov <ruslanr26@mail.ru>, Ekaterina Karmanova <monitor81@mail.ru>
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.1.2"
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.com"},
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"
@@ -0,0 +1,4 @@
1
+ from docreader.classifier.base import BaseClassifier, ClassifiedDocument
2
+ from docreader.classifier.yolo_classifier import DocClassifier
3
+
4
+ __all__ = ["BaseClassifier", "ClassifiedDocument", "DocClassifier"]
@@ -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 predict(self, image: np.ndarray) -> tuple[str, float]:
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
- import torch
46
- return "cuda" if torch.cuda.is_available() else "cpu"
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,4 @@
1
+ from docreader.detector.base import BaseDetector, Detection
2
+ from docreader.detector.yolo_obb import ZoneDetector
3
+
4
+ __all__ = ["BaseDetector", "Detection", "ZoneDetector"]
@@ -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
- "best_doc_classifier.pth": {
24
- "url": f"{_BASE_URL}/best_doc_classifier.pth",
25
- "sha256": "6d56f45bd33f5296f40bbf32c67a46c01914a3ac7a3dcbbf9aa9a0b8402b59c4",
26
- "size_mb": 8.75,
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
@@ -0,0 +1,4 @@
1
+ from docreader.ocr.base import BaseOcrEngine, OcrResult
2
+ from docreader.ocr.easyocr_engine import TextRecognizer
3
+
4
+ __all__ = ["BaseOcrEngine", "OcrResult", "TextRecognizer"]