matrice-analytics 0.1.60__py3-none-any.whl

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 (196) hide show
  1. matrice_analytics/__init__.py +28 -0
  2. matrice_analytics/boundary_drawing_internal/README.md +305 -0
  3. matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
  4. matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
  5. matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
  6. matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
  7. matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
  8. matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
  9. matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
  10. matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
  11. matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
  12. matrice_analytics/post_processing/README.md +455 -0
  13. matrice_analytics/post_processing/__init__.py +732 -0
  14. matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
  15. matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
  16. matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
  17. matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
  18. matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
  19. matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
  20. matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
  21. matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
  22. matrice_analytics/post_processing/config.py +146 -0
  23. matrice_analytics/post_processing/core/__init__.py +63 -0
  24. matrice_analytics/post_processing/core/base.py +704 -0
  25. matrice_analytics/post_processing/core/config.py +3291 -0
  26. matrice_analytics/post_processing/core/config_utils.py +925 -0
  27. matrice_analytics/post_processing/face_reg/__init__.py +43 -0
  28. matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
  29. matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  42. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  43. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  44. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  45. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  46. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  47. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  48. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  49. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  50. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  51. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  52. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  53. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  54. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  55. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  56. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  57. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  58. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  59. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  60. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  61. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  62. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  63. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  64. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  65. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  66. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  67. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  68. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  69. matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
  70. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  71. matrice_analytics/post_processing/post_processor.py +1175 -0
  72. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  73. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  74. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  75. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  76. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  77. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  78. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  79. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  80. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  81. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  82. matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
  83. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  84. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  85. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  86. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  87. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  88. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  89. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  90. matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
  91. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  92. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  93. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  94. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  95. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  96. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  97. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  98. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  99. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  100. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  101. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  102. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  103. matrice_analytics/post_processing/usecases/color/clip.py +660 -0
  104. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  105. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  106. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  107. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  108. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  109. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  110. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  111. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  112. matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
  113. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  114. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  115. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  116. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  117. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  118. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  119. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
  120. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  121. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  122. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  123. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  124. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  125. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  126. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  127. matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
  128. matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
  129. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  130. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  131. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  132. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  133. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  134. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  135. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  136. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  137. matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
  138. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
  139. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  140. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  141. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  142. matrice_analytics/post_processing/usecases/parking.py +787 -0
  143. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  144. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  145. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  146. matrice_analytics/post_processing/usecases/people_counting.py +706 -0
  147. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  148. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  149. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  150. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  151. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  152. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  153. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  154. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  155. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  156. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  157. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  158. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  159. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  160. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  161. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  162. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  163. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  164. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  165. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  166. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  167. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  168. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  169. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  170. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
  171. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  172. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  173. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  174. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  175. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  176. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  177. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  178. matrice_analytics/post_processing/utils/__init__.py +150 -0
  179. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  180. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  181. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  182. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  183. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  184. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  185. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  186. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  187. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  188. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  189. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  190. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  191. matrice_analytics/py.typed +0 -0
  192. matrice_analytics-0.1.60.dist-info/METADATA +481 -0
  193. matrice_analytics-0.1.60.dist-info/RECORD +196 -0
  194. matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
  195. matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
  196. matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
@@ -0,0 +1,214 @@
1
+ """
2
+ Utility functions module
3
+ """
4
+
5
+ import logging
6
+ import pathlib
7
+ import pkgutil
8
+ import random
9
+ from collections.abc import Iterator
10
+ from importlib import import_module
11
+ from typing import Optional, Union
12
+
13
+ import cv2
14
+ import keras
15
+ import numpy as np
16
+ import numpy.typing as npt
17
+
18
+ from fast_plate_ocr.core.process import read_and_resize_plate_image
19
+ from fast_plate_ocr.core.types import ImageColorMode, ImageInterpolation, PaddingColor
20
+ from fast_plate_ocr.train.model.config import PlateOCRConfig
21
+ from fast_plate_ocr.train.model.loss import cce_loss, focal_cce_loss
22
+ from fast_plate_ocr.train.model.metric import (
23
+ cat_acc_metric,
24
+ plate_acc_metric,
25
+ plate_len_acc_metric,
26
+ top_3_k_metric,
27
+ )
28
+
29
+
30
+ def one_hot_plate(plate: str, alphabet: str) -> list[list[int]]:
31
+ return [[0 if char != letter else 1 for char in alphabet] for letter in plate]
32
+
33
+
34
+ def target_transform(
35
+ plate_text: str,
36
+ max_plate_slots: int,
37
+ alphabet: str,
38
+ pad_char: str,
39
+ ) -> npt.NDArray[np.uint8]:
40
+ # Pad the plates which length is smaller than 'max_plate_slots'
41
+ plate_text = plate_text.ljust(max_plate_slots, pad_char)
42
+ # Generate numpy arrays with one-hot encoding of plates
43
+ encoded_plate = np.array(one_hot_plate(plate_text, alphabet=alphabet), dtype=np.uint8)
44
+ return encoded_plate
45
+
46
+
47
+ def _register_custom_keras():
48
+ base_pkg = "fast_plate_ocr.train.model"
49
+ for _, name, _ in pkgutil.walk_packages(
50
+ import_module(base_pkg).__path__, prefix=f"{base_pkg}."
51
+ ):
52
+ if any(m in name for m in ("layers",)):
53
+ import_module(name)
54
+
55
+
56
+ def load_keras_model(
57
+ model_path: Union[str, pathlib.Path],
58
+ plate_config: PlateOCRConfig,
59
+ ) -> keras.Model:
60
+ """
61
+ Utility helper function to load the keras OCR model.
62
+ """
63
+ _register_custom_keras()
64
+ custom_objects = {
65
+ "cce": cce_loss(
66
+ vocabulary_size=plate_config.vocabulary_size,
67
+ ),
68
+ "focal_cce": focal_cce_loss(
69
+ vocabulary_size=plate_config.vocabulary_size,
70
+ ),
71
+ "cat_acc": cat_acc_metric(
72
+ max_plate_slots=plate_config.max_plate_slots,
73
+ vocabulary_size=plate_config.vocabulary_size,
74
+ ),
75
+ "plate_acc": plate_acc_metric(
76
+ max_plate_slots=plate_config.max_plate_slots,
77
+ vocabulary_size=plate_config.vocabulary_size,
78
+ ),
79
+ "top_3_k": top_3_k_metric(
80
+ vocabulary_size=plate_config.vocabulary_size,
81
+ ),
82
+ "plate_len_acc": plate_len_acc_metric(
83
+ max_plate_slots=plate_config.max_plate_slots,
84
+ vocabulary_size=plate_config.vocabulary_size,
85
+ pad_token_index=plate_config.pad_idx,
86
+ ),
87
+ }
88
+ model = keras.models.load_model(model_path, custom_objects=custom_objects)
89
+ return model
90
+
91
+
92
+ IMG_EXTENSIONS: set[str] = {".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".webp"}
93
+ """Valid image extensions for the scope of this script."""
94
+
95
+
96
+ def load_images_from_folder( # noqa: PLR0913
97
+ img_dir: pathlib.Path,
98
+ width: int,
99
+ height: int,
100
+ image_color_mode: ImageColorMode = "grayscale",
101
+ keep_aspect_ratio: bool = False,
102
+ interpolation_method: ImageInterpolation = "linear",
103
+ padding_color: PaddingColor = (114, 114, 114),
104
+ shuffle: bool = False,
105
+ limit: Optional[int] = None,
106
+ ) -> Iterator[npt.NDArray]:
107
+ """
108
+ Return all images read from a directory. This uses the same read function used during training.
109
+ """
110
+ # pylint: disable=too-many-arguments
111
+ image_paths = sorted(
112
+ str(f.resolve()) for f in img_dir.iterdir() if f.is_file() and f.suffix in IMG_EXTENSIONS
113
+ )
114
+ if limit:
115
+ image_paths = image_paths[:limit]
116
+ if shuffle:
117
+ random.shuffle(image_paths)
118
+ yield from (
119
+ read_and_resize_plate_image(
120
+ i,
121
+ img_height=height,
122
+ img_width=width,
123
+ image_color_mode=image_color_mode,
124
+ keep_aspect_ratio=keep_aspect_ratio,
125
+ interpolation_method=interpolation_method,
126
+ padding_color=padding_color,
127
+ )
128
+ for i in image_paths
129
+ )
130
+
131
+
132
+ def postprocess_model_output(
133
+ prediction: npt.NDArray,
134
+ alphabet: str,
135
+ max_plate_slots: int,
136
+ vocab_size: int,
137
+ ) -> tuple[str, npt.NDArray]:
138
+ """
139
+ Return plate text and confidence scores from raw model output.
140
+ """
141
+ prediction = prediction.reshape((max_plate_slots, vocab_size))
142
+ probs = np.max(prediction, axis=-1)
143
+ prediction = np.argmax(prediction, axis=-1)
144
+ plate = "".join([alphabet[x] for x in prediction])
145
+ return plate, probs
146
+
147
+
148
+ def low_confidence_positions(probs, thresh=0.3) -> npt.NDArray:
149
+ """Returns indices of elements in `probs` less than `thresh`, indicating low confidence."""
150
+ return np.where(np.array(probs) < thresh)[0]
151
+
152
+
153
+ def display_predictions(
154
+ image: npt.NDArray,
155
+ plate: str,
156
+ probs: npt.NDArray,
157
+ low_conf_thresh: float,
158
+ ) -> None:
159
+ """
160
+ Display plate and corresponding prediction.
161
+ """
162
+ plate_str = "".join(plate)
163
+ logging.info("Plate: %s", plate_str)
164
+ logging.info("Confidence: %s", probs)
165
+ image_to_show = cv2.resize(image, None, fx=3, fy=3, interpolation=cv2.INTER_LINEAR)
166
+ if len(image_to_show.shape) == 2:
167
+ image_to_show = cv2.cvtColor(image_to_show, cv2.COLOR_GRAY2RGB)
168
+ elif image_to_show.shape[2] == 3:
169
+ image_to_show = cv2.cvtColor(image_to_show, cv2.COLOR_BGR2RGB)
170
+ # Average probabilities
171
+ avg_prob = np.mean(probs) * 100
172
+ cv2.putText(
173
+ image_to_show,
174
+ f"{plate_str} {avg_prob:.{2}f}%",
175
+ org=(5, 30),
176
+ fontFace=cv2.FONT_HERSHEY_SIMPLEX,
177
+ fontScale=1,
178
+ color=(0, 0, 0),
179
+ lineType=1,
180
+ thickness=6,
181
+ )
182
+ cv2.putText(
183
+ image_to_show,
184
+ f"{plate_str} {avg_prob:.{2}f}%",
185
+ org=(5, 30),
186
+ fontFace=cv2.FONT_HERSHEY_SIMPLEX,
187
+ fontScale=1,
188
+ color=(255, 255, 255),
189
+ lineType=1,
190
+ thickness=2,
191
+ )
192
+ # Display character with low confidence
193
+ low_conf_chars = "Low conf. on: " + " ".join(
194
+ [plate[i] for i in low_confidence_positions(probs, thresh=low_conf_thresh)]
195
+ )
196
+ cv2.putText(
197
+ image_to_show,
198
+ low_conf_chars,
199
+ org=(5, 200),
200
+ fontFace=cv2.FONT_HERSHEY_SIMPLEX,
201
+ fontScale=0.7,
202
+ color=(0, 0, 220),
203
+ lineType=1,
204
+ thickness=2,
205
+ )
206
+ try:
207
+ cv2.imshow("plates", image_to_show)
208
+ if cv2.waitKey(0) & 0xFF == ord("q"):
209
+ return
210
+ except cv2.error as e: # pylint: disable=catching-non-exception
211
+ raise RuntimeError( # pylint: disable=bad-exception-cause
212
+ "This visualization requires full OpenCV with GUI support. "
213
+ "Install with `pip install opencv-python` instead of headless."
214
+ ) from e
@@ -0,0 +1,270 @@
1
+ import re
2
+ import logging
3
+
4
+ class TextPostprocessor:
5
+ def __init__(self, logging_level=logging.INFO):
6
+ """
7
+ Initialize the text postprocessor with optional logging configuration.
8
+
9
+ Args:
10
+ logging_level: The level of logging detail. Default is INFO.
11
+ """
12
+ self.logger = logging.getLogger('TextPostprocessor')
13
+
14
+ self.task_processors = {
15
+ "license_plate": self._process_license_plate,
16
+ "license_plate_india": self._process_license_plate_india,
17
+ "license_plate_us": self._process_license_plate_us,
18
+ "license_plate_eu": self._process_license_plate_eu,
19
+ "license_plate_qatar": self._process_license_plate_qatar,
20
+ }
21
+
22
+ self.char_substitutions = {
23
+ 'O': '0',
24
+ 'o': '0',
25
+ 'I': '1',
26
+ 'Z': '2',
27
+ 'A': '4',
28
+ 'L': '1',
29
+ 'AV': 'AV',
30
+ 'S': '5',
31
+ 'B': '8',
32
+ 'D': '0',
33
+ 'Q': '0',
34
+ 'G': '6',
35
+ 'T': '7'
36
+ }
37
+
38
+ def postprocess(self, texts, confidences, task=None, confidence_threshold=0.25, cleanup=True, region=None):
39
+ """
40
+ Postprocesses the extracted text by cleaning and filtering low-confidence results.
41
+ Applies task-specific processing if a task is specified.
42
+
43
+ Args:
44
+ texts (list): List of extracted text strings.
45
+ confidences (list): List of confidence scores corresponding to each text.
46
+ task (str): Specific task for customized postprocessing. Default is None.
47
+ confidence_threshold (float): Minimum confidence required to keep the text. Default is 0.5.
48
+ cleanup (bool): Whether to perform text cleanup.
49
+ region (str): Specific region for license plate processing ('india', 'us', 'eu', 'qatar'). Default is None.
50
+
51
+ Returns:
52
+ list: List of processed texts with corresponding confidence scores and validity flags.
53
+ """
54
+ results = []
55
+
56
+ for text, confidence in zip(texts, confidences):
57
+ if confidence < confidence_threshold:
58
+ self.logger.debug(f"Text '{text}' rejected: confidence {confidence} below threshold {confidence_threshold}")
59
+ results.append((None, confidence, False))
60
+ continue
61
+
62
+ if cleanup:
63
+ processed_text = self._clean_text(text)
64
+ else:
65
+ processed_text = text
66
+
67
+ if task and processed_text:
68
+ if task == "license_plate" and region:
69
+ region_task = f"license_plate_{region.lower()}"
70
+ if region_task in self.task_processors:
71
+ processed_text = self.task_processors[region_task](processed_text)
72
+ else:
73
+ processed_text = self.task_processors["license_plate"](processed_text)
74
+ self.logger.warning(f"Region '{region}' not supported, using generic license plate processor")
75
+ elif task in self.task_processors:
76
+ processed_text = self.task_processors[task](processed_text)
77
+ else:
78
+ self.logger.warning(f"Task '{task}' not supported, skipping task-specific processing")
79
+
80
+ if processed_text:
81
+ self.logger.debug(f"Text processed successfully: '{text}' -> '{processed_text}'")
82
+ results.append((processed_text, confidence, True))
83
+ else:
84
+ self.logger.debug(f"Text '{text}' rejected during processing")
85
+ results.append((None, confidence, False))
86
+
87
+ return results
88
+
89
+ def _clean_text(self, text):
90
+ """
91
+ Basic text cleaning operations.
92
+
93
+ Args:
94
+ text (str): Text to clean.
95
+
96
+ Returns:
97
+ str: Cleaned text.
98
+ """
99
+ clean_text = text.strip()
100
+ clean_text = ''.join(char for char in clean_text if char.isprintable())
101
+ clean_text = ' '.join(clean_text.split())
102
+
103
+ return clean_text
104
+
105
+ def _process_license_plate(self, text):
106
+ """
107
+ Generic license plate processor that respects the specified region.
108
+
109
+ Args:
110
+ text (str): License plate text to process.
111
+
112
+ Returns:
113
+ str: Processed license plate text or None if invalid.
114
+ """
115
+ plate_text = text.upper()
116
+ plate_text = ''.join(plate_text.split())
117
+
118
+ if self.region and self.region.lower() == 'qatar':
119
+ return self._process_license_plate_qatar(plate_text)
120
+ elif self.region and self.region.lower() == 'india':
121
+ return self._process_license_plate_india(plate_text)
122
+ elif self.region and self.region.lower() == 'us':
123
+ return self._process_license_plate_us(plate_text)
124
+ elif self.region and self.region.lower() == 'eu':
125
+ return self._process_license_plate_eu(plate_text)
126
+ else:
127
+ if re.match(r'^[A-Z]{2}\d{1,2}[A-Z]{1,2}\d{4}$', plate_text):
128
+ return self._process_license_plate_india(plate_text)
129
+ elif re.match(r'^[A-Z0-9]{1,8}$', plate_text) and len(plate_text) <= 8:
130
+ return self._process_license_plate_us(plate_text)
131
+ elif re.match(r'^[A-Z]{1,3}[-\s]?[A-Z0-9]{1,4}[-\s]?[A-Z0-9]{1,3}$', plate_text):
132
+ return self._process_license_plate_eu(plate_text)
133
+ elif re.match(r'^\d{1,6}\s*[A-Z]+?$', plate_text):
134
+ return self._process_license_plate_qatar(plate_text)
135
+ else:
136
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
137
+ if 4 <= len(plate_text) <= 10:
138
+ return plate_text
139
+
140
+ self.logger.warning(f"Could not identify license plate format: '{text}'")
141
+ return None
142
+
143
+ def _process_license_plate_india(self, text):
144
+ plate_text = text.upper().replace(" ", "")
145
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
146
+ for old, new in self.char_substitutions.items():
147
+ plate_text = plate_text.replace(old, new)
148
+
149
+ if len(plate_text) >= 7:
150
+ state_code = plate_text[:2]
151
+ rest = plate_text[2:]
152
+ match = re.match(r'^(\d{1,2})[ -]?([A-Z]{1,2})[ -]?(\d{4})$', rest)
153
+ if match and state_code in ['AN', 'AP', 'AR', 'AS', 'BR', 'CH', 'CG', 'DD', 'DL', 'GA', 'GJ', 'HP', 'HR', 'JH', 'JK', 'KA', 'KL', 'LA', 'LD', 'MH', 'ML', 'MN', 'MP', 'MZ', 'NL', 'OD', 'PB', 'PY', 'RJ', 'SK', 'TN', 'TR', 'TG', 'TS', 'UK', 'UP', 'WB']:
154
+ district, series, number = match.groups()
155
+ formatted_plate = f"{state_code}{district}{series}{number}"
156
+ self.logger.info(f"Processed Indian license plate: '{text}' -> '{formatted_plate}'")
157
+ return formatted_plate
158
+ self.logger.warning(f"Invalid Indian license plate format: '{text}'")
159
+ return None
160
+
161
+ def _process_license_plate_us(self, text):
162
+ plate_text = text.upper()
163
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
164
+
165
+ for old, new in self.char_substitutions.items():
166
+ plate_text = plate_text.replace(old, new)
167
+
168
+ if re.match(r'^[A-Z]{3}\d{4}$', plate_text) or re.match(r'^\d{3}[A-Z]{4}$', plate_text):
169
+ self.logger.info(f"Processed US license plate (standard format): '{text}' -> '{plate_text}'")
170
+ return plate_text
171
+ if 2 <= len(plate_text) <= 8 and re.match(r'^[A-Z0-9]+$', plate_text):
172
+ self.logger.info(f"Processed US license plate (vanity/other format): '{text}' -> '{plate_text}'")
173
+ return plate_text
174
+
175
+ self.logger.warning(f"Invalid US license plate format: '{text}'")
176
+ return None
177
+
178
+ def _process_license_plate_eu(self, text):
179
+ plate_text = text.upper()
180
+ plate_text = ''.join(char for char in plate_text if char.isalnum() or char == '-')
181
+
182
+ if '-' not in plate_text and len(plate_text) > 3:
183
+ for i in range(1, 4):
184
+ if i < len(plate_text) and plate_text[i].isdigit() and plate_text[i-1].isalpha():
185
+ plate_text = plate_text[:i] + '-' + plate_text[i:]
186
+ break
187
+
188
+ for old, new in self.char_substitutions.items():
189
+ plate_text = plate_text.replace(old, new)
190
+
191
+ if re.match(r'^[A-Z]{1,3}-[A-Z]{1,2}\d{1,4}$', plate_text):
192
+ self.logger.info(f"Processed German license plate: '{text}' -> '{plate_text}'")
193
+ return plate_text
194
+ if re.match(r'^[A-Z]{2}\d{2}[A-Z]{3}$', plate_text):
195
+ self.logger.info(f"Processed UK license plate: '{text}' -> '{plate_text}'")
196
+ return plate_text
197
+ if re.match(r'^[A-Z]{2}-\d{3}-[A-Z]{2}$', plate_text) or re.match(r'^\d{4}[A-Z]{3}$', plate_text):
198
+ self.logger.info(f"Processed French license plate: '{text}' -> '{plate_text}'")
199
+ return plate_text
200
+ if re.match(r'^[A-Z]{2}\d{3}[A-Z]{2}$', plate_text):
201
+ self.logger.info(f"Processed Italian license plate: '{text}' -> '{plate_text}'")
202
+ return plate_text
203
+ if re.match(r'^\d{4}[BCDFGHJKLMNPRSTVWXYZ]{3}$', plate_text):
204
+ self.logger.info(f"Processed Spanish license plate: '{text}' -> '{plate_text}'")
205
+ return plate_text
206
+ if re.search(r'[A-Z]', plate_text) and re.search(r'\d', plate_text) and 4 <= len(plate_text) <= 10:
207
+ self.logger.info(f"Processed generic European license plate: '{text}' -> '{plate_text}'")
208
+ return plate_text
209
+
210
+ self.logger.warning(f"Invalid European license plate format: '{text}'")
211
+ return None
212
+
213
+ def _process_license_plate_qatar(self, text):
214
+ """
215
+ Process Qatar license plate text by converting Arabic numerals to Latin and keeping only digits.
216
+
217
+ Args:
218
+ text (str): License plate text to process.
219
+
220
+ Returns:
221
+ str: Processed license plate text or None if invalid.
222
+ """
223
+ # Check for Unicode escape sequences (e.g., \u0664)
224
+ if r'\u' in str(text):
225
+ self.logger.warning(f"Invalid Qatar license plate format: '{text}' contains Unicode escape sequence")
226
+ return None
227
+
228
+ # Define Arabic to Latin numeral mapping
229
+ arabic_to_latin = str.maketrans('٠١٢٣٤٥٦٧٨٩', '0123456789')
230
+
231
+ # Convert Arabic numerals to Latin and keep only alphanumeric characters
232
+ plate_text = text.translate(arabic_to_latin)
233
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
234
+
235
+ # Apply character substitutions for common OCR errors
236
+ for old, new in self.char_substitutions.items():
237
+ plate_text = plate_text.replace(old, new)
238
+
239
+ # Keep only digits for Qatar license plates
240
+ plate_text = ''.join(char for char in plate_text if char.isdigit())
241
+
242
+ # Validate: Ensure the text is 1 to 6 digits
243
+ if re.match(r'^\d{1,6}$', plate_text):
244
+ self.logger.info(f"Processed Qatar license plate: '{text}' -> '{plate_text}'")
245
+ return plate_text
246
+
247
+ self.logger.warning(f"Invalid Qatar license plate format: '{text}'")
248
+ return None
249
+
250
+ def _string_similarity(self, s1, s2):
251
+ if len(s1) > len(s2):
252
+ s1, s2 = s2, s1
253
+
254
+ distances = range(len(s1) + 1)
255
+ for i2, c2 in enumerate(s2):
256
+ distances_ = [i2+1]
257
+ for i1, c1 in enumerate(s1):
258
+ if c1 == c2:
259
+ distances_.append(distances[i1])
260
+ else:
261
+ distances_.append(1 + min((distances[i1], distances[i1 + 1], distances_[-1])))
262
+ distances = distances_
263
+
264
+ max_len = max(len(s1), len(s2))
265
+ similarity = 1 - (distances[-1] / max_len if max_len > 0 else 0)
266
+ return similarity
267
+
268
+ def add_task_processor(self, task_name, processor_function):
269
+ self.task_processors[task_name] = processor_function
270
+ self.logger.info(f"Added new task processor: {task_name}")
@@ -0,0 +1,52 @@
1
+ import cv2
2
+ import numpy as np
3
+
4
+ class ImagePreprocessor:
5
+ def __init__(self):
6
+ """Initialize the image preprocessor"""
7
+ pass
8
+
9
+ def preprocess(self, image_np, resize_dim=None, grayscale=True):
10
+ """
11
+ Preprocesses the image with various operations.
12
+
13
+ Args:
14
+ image_np (np.ndarray): Input image as a numpy array.
15
+ resize_dim (tuple): Desired dimensions (width, height). If None, no resizing is done.
16
+ grayscale (bool): Whether to convert the image to grayscale.
17
+
18
+ Returns:
19
+ np.ndarray: Preprocessed image.
20
+ """
21
+ processed_image = image_np.copy()
22
+
23
+ # Convert to grayscale if requested
24
+ if grayscale:
25
+ if len(processed_image.shape) == 3: # Check if image is already grayscale
26
+ processed_image = cv2.cvtColor(processed_image, cv2.COLOR_RGB2GRAY)
27
+
28
+ # Resize image if dimensions are provided
29
+ if resize_dim:
30
+ processed_image = cv2.resize(processed_image, resize_dim, interpolation=cv2.INTER_LINEAR)
31
+
32
+ return processed_image
33
+
34
+ def crop_to_bboxes(self, image_np, bboxes):
35
+ """
36
+ Crops the image to the specified bounding boxes.
37
+
38
+ Args:
39
+ image_np (np.ndarray): Input image as a numpy array.
40
+ bboxes (list): List of bounding boxes. Each box is a list of [xmin, ymin, xmax, ymax].
41
+
42
+ Returns:
43
+ list: List of cropped images.
44
+ """
45
+ cropped_images = []
46
+
47
+ for box in bboxes:
48
+ xmin, ymin, xmax, ymax = map(int, box)
49
+ cropped_img = image_np[ymin:ymax, xmin:xmax]
50
+ cropped_images.append(cropped_img)
51
+
52
+ return cropped_images