matrice-analytics 0.1.2__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.

Potentially problematic release.


This version of matrice-analytics might be problematic. Click here for more details.

Files changed (160) 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 +142 -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 +3188 -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 +681 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +1870 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +339 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +283 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +248 -0
  35. matrice_analytics/post_processing/ocr/postprocessing.py +271 -0
  36. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  37. matrice_analytics/post_processing/post_processor.py +1153 -0
  38. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  39. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  40. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  41. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  42. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  43. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  44. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  45. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  46. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  47. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  48. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  49. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  50. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  51. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  52. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  53. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  54. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  55. matrice_analytics/post_processing/usecases/age_gender_detection.py +1043 -0
  56. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  57. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  58. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  59. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  60. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  61. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  62. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  63. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  64. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  65. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  66. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  67. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  68. matrice_analytics/post_processing/usecases/color/clip.py +232 -0
  69. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  70. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  71. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  72. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  73. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  74. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  75. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  76. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  77. matrice_analytics/post_processing/usecases/color_detection.py +1835 -0
  78. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  79. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  80. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  81. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  82. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  83. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  84. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +930 -0
  85. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  86. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  87. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  88. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  89. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  90. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  91. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  92. matrice_analytics/post_processing/usecases/fire_detection.py +1112 -0
  93. matrice_analytics/post_processing/usecases/flare_analysis.py +891 -0
  94. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  95. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  96. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  97. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  98. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  99. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  100. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  101. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  102. matrice_analytics/post_processing/usecases/license_plate_detection.py +914 -0
  103. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1194 -0
  104. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  105. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  106. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  107. matrice_analytics/post_processing/usecases/parking.py +787 -0
  108. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  109. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  110. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  111. matrice_analytics/post_processing/usecases/people_counting.py +1728 -0
  112. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  113. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  114. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  115. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  116. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  117. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  118. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  119. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  120. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  121. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  122. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  123. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  124. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  125. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  126. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  127. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  128. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  129. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  130. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  131. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  132. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  133. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  134. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +950 -0
  135. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  136. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  137. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  138. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  139. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  140. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  141. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  142. matrice_analytics/post_processing/utils/__init__.py +150 -0
  143. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  144. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  145. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  146. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  147. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  148. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  149. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  150. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  151. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  152. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  153. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  154. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  155. matrice_analytics/py.typed +0 -0
  156. matrice_analytics-0.1.2.dist-info/METADATA +481 -0
  157. matrice_analytics-0.1.2.dist-info/RECORD +160 -0
  158. matrice_analytics-0.1.2.dist-info/WHEEL +5 -0
  159. matrice_analytics-0.1.2.dist-info/licenses/LICENSE.txt +21 -0
  160. matrice_analytics-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,248 @@
1
+ import easyocr
2
+ import numpy as np
3
+ import torch
4
+
5
+ class EasyOCRExtractor:
6
+ def __init__(self, lang=['en', 'hi', 'ar'], gpu=False, model_storage_directory=None,
7
+ download_enabled=True, detector=True, recognizer=True, verbose=False):
8
+ """
9
+ Initializes the EasyOCR text extractor with optimized parameters.
10
+
11
+ Args:
12
+ lang (str or list): Language(s) to be used by EasyOCR. Default is ['en', 'hi', 'ar'].
13
+ gpu (bool): Enable GPU acceleration if available. Default is True.
14
+ model_storage_directory (str): Custom path to store models. Default is None.
15
+ download_enabled (bool): Allow downloading models if not found. Default is True.
16
+ detector (bool): Load text detection model. Default is True.
17
+ recognizer (bool): Load text recognition model. Default is True.
18
+ verbose (bool): Enable verbose output (e.g., progress bars). Default is False.
19
+ """
20
+ self.lang = lang
21
+ self.gpu = gpu
22
+ # Check if GPU is available
23
+ if torch.cuda.is_available():
24
+ self.gpu = True
25
+ else:
26
+ self.gpu = False
27
+ self.model_storage_directory = model_storage_directory
28
+ self.download_enabled = download_enabled
29
+ self.detector = detector
30
+ self.recognizer = recognizer
31
+ self.verbose = verbose
32
+ self.reader = None
33
+
34
+ def setup(self):
35
+ """
36
+ Initializes the EasyOCR reader if not already initialized.
37
+ """
38
+ if self.reader is None:
39
+ lang_list = [self.lang] if isinstance(self.lang, str) else self.lang
40
+ self.reader = easyocr.Reader(
41
+ lang_list=lang_list,
42
+ gpu=self.gpu,
43
+ model_storage_directory=self.model_storage_directory,
44
+ download_enabled=self.download_enabled,
45
+ detector=self.detector,
46
+ recognizer=self.recognizer,
47
+ verbose=self.verbose
48
+ )
49
+
50
+ def extract(self, image_np, bboxes=None, detail=1, paragraph=False,
51
+ decoder='greedy', beam_width=5, batch_size=1, workers=0,
52
+ allowlist=None, blocklist=None, min_size=10, rotation_info=None,
53
+ contrast_ths=0.1, adjust_contrast=0.5, text_threshold=0.7,
54
+ low_text=0.4, link_threshold=0.4, canvas_size=2560, mag_ratio=1.0,
55
+ slope_ths=0.1, ycenter_ths=0.5, height_ths=0.5, width_ths=0.5,
56
+ add_margin=0.1):
57
+ """
58
+ Extracts text from the given image or specific regions within the bounding boxes
59
+ with configurable parameters for optimal performance.
60
+
61
+ Args:
62
+ image_np (np.ndarray): Input image as a numpy array.
63
+ bboxes (list): List of bounding boxes. Each box is a list of [xmin, ymin, xmax, ymax].
64
+ If None, OCR is performed on the entire image.
65
+ detail (int): Set to 0 for simple output, 1 for detailed output.
66
+ paragraph (bool): Combine results into paragraphs.
67
+ decoder (str): Decoding method ('greedy', 'beamsearch', 'wordbeamsearch').
68
+ beam_width (int): How many beams to keep when using beam search decoders.
69
+ batch_size (int): Number of images to process in a batch.
70
+ workers (int): Number of worker threads for data loading.
71
+ allowlist (str): Force recognition of only specific characters.
72
+ blocklist (str): Block specific characters from recognition.
73
+ min_size (int): Filter text boxes smaller than this pixel size.
74
+ rotation_info (list): List of rotation angles to try (e.g., [90, 180, 270]).
75
+ contrast_ths (float): Threshold for contrast adjustment.
76
+ adjust_contrast (float): Target contrast level for low-contrast text.
77
+ text_threshold (float): Text confidence threshold.
78
+ low_text (float): Text low-bound score.
79
+ link_threshold (float): Link confidence threshold.
80
+ canvas_size (int): Maximum image size before resizing.
81
+ mag_ratio (float): Image magnification ratio.
82
+ slope_ths (float): Maximum slope for merging boxes.
83
+ ycenter_ths (float): Maximum y-center shift for merging boxes.
84
+ height_ths (float): Maximum height difference for merging boxes.
85
+ width_ths (float): Maximum width for horizontal merging.
86
+ add_margin (float): Margin to add around text boxes.
87
+
88
+ Returns:
89
+ list: OCR results containing text, confidence, and bounding boxes.
90
+ """
91
+ # Make sure the reader is initialized
92
+ self.setup()
93
+
94
+ ocr_results = []
95
+
96
+ # Dictionary of readtext parameters
97
+ readtext_params = {
98
+ 'decoder': decoder,
99
+ 'beamWidth': beam_width,
100
+ 'batch_size': batch_size,
101
+ 'workers': workers,
102
+ 'allowlist': allowlist,
103
+ 'blocklist': blocklist,
104
+ 'detail': detail,
105
+ 'paragraph': paragraph,
106
+ 'min_size': min_size,
107
+ 'rotation_info': rotation_info,
108
+ 'contrast_ths': contrast_ths,
109
+ 'adjust_contrast': adjust_contrast,
110
+ 'text_threshold': text_threshold,
111
+ 'low_text': low_text,
112
+ 'link_threshold': link_threshold,
113
+ 'canvas_size': canvas_size,
114
+ 'mag_ratio': mag_ratio,
115
+ 'slope_ths': slope_ths,
116
+ 'ycenter_ths': ycenter_ths,
117
+ 'height_ths': height_ths,
118
+ 'width_ths': width_ths,
119
+ 'add_margin': add_margin
120
+ }
121
+
122
+ # If no bounding boxes, perform OCR on the entire image
123
+ if bboxes is None:
124
+ text_data = self.reader.readtext(image_np, **readtext_params)
125
+ if detail == 0:
126
+ return text_data # Simple output format for detail=0
127
+
128
+ for bbox, text, conf in text_data:
129
+ ocr_results.append({
130
+ "text": text,
131
+ "confidence": conf,
132
+ "bounding_box": bbox
133
+ })
134
+ else:
135
+ # Perform OCR on each bounding box
136
+ for box in bboxes:
137
+ xmin, ymin, xmax, ymax = map(int, box)
138
+ cropped_img = image_np[ymin:ymax, xmin:xmax]
139
+
140
+ # Skip empty crops
141
+ if cropped_img.size == 0 or cropped_img.shape[0] == 0 or cropped_img.shape[1] == 0:
142
+ continue
143
+
144
+ text_data = self.reader.readtext(cropped_img, **readtext_params)
145
+
146
+ if detail == 0:
147
+ # Adjust coordinates for the cropped region
148
+ adjusted_data = []
149
+ for result in text_data:
150
+ if isinstance(result, tuple) and len(result) >= 1:
151
+ # Adjust coordinates based on crop position
152
+ adjusted_bbox = [[pt[0] + xmin, pt[1] + ymin] for pt in result[0]]
153
+ if len(result) == 3: # (bbox, text, confidence)
154
+ adjusted_data.append((adjusted_bbox, result[1], result[2]))
155
+ elif len(result) == 2: # (bbox, text)
156
+ adjusted_data.append((adjusted_bbox, result[1]))
157
+ ocr_results.extend(adjusted_data)
158
+ else:
159
+ for bbox, text, conf in text_data:
160
+ # Adjust bounding box coordinates relative to the original image
161
+ adjusted_bbox = [
162
+ [pt[0] + xmin, pt[1] + ymin] for pt in bbox
163
+ ]
164
+
165
+ ocr_results.append({
166
+ "text": text,
167
+ "confidence": conf,
168
+ "bounding_box": adjusted_bbox
169
+ })
170
+
171
+ return ocr_results
172
+
173
+ def detect_text_regions(self, image_np, min_size=10, text_threshold=0.7,
174
+ low_text=0.4, link_threshold=0.4, canvas_size=2560,
175
+ mag_ratio=1.0, slope_ths=0.1, ycenter_ths=0.5,
176
+ height_ths=0.5, width_ths=0.5, add_margin=0.1,
177
+ optimal_num_chars=None):
178
+ """
179
+ Detects text regions in the image without performing recognition.
180
+
181
+ Args:
182
+ image_np (np.ndarray): Input image as a numpy array.
183
+ min_size (int): Filter text boxes smaller than this pixel size.
184
+ text_threshold (float): Text confidence threshold.
185
+ low_text (float): Text low-bound score.
186
+ link_threshold (float): Link confidence threshold.
187
+ canvas_size (int): Maximum image size before resizing.
188
+ mag_ratio (float): Image magnification ratio.
189
+ slope_ths (float): Maximum slope for merging boxes.
190
+ ycenter_ths (float): Maximum y-center shift for merging boxes.
191
+ height_ths (float): Maximum height difference for merging boxes.
192
+ width_ths (float): Maximum width for horizontal merging.
193
+ add_margin (float): Margin to add around text boxes.
194
+ optimal_num_chars (int): Prioritize boxes with this estimated character count.
195
+
196
+ Returns:
197
+ tuple: (horizontal_list, free_list) containing text regions
198
+ """
199
+ self.setup()
200
+ return self.reader.detect(
201
+ image_np,
202
+ min_size=min_size,
203
+ text_threshold=text_threshold,
204
+ low_text=low_text,
205
+ link_threshold=link_threshold,
206
+ canvas_size=canvas_size,
207
+ mag_ratio=mag_ratio,
208
+ slope_ths=slope_ths,
209
+ ycenter_ths=ycenter_ths,
210
+ height_ths=height_ths,
211
+ width_ths=width_ths,
212
+ add_margin=add_margin,
213
+ optimal_num_chars=optimal_num_chars
214
+ )
215
+
216
+ def recognize_from_regions(self, image_np, horizontal_list=None, free_list=None,
217
+ decoder='greedy', beam_width=5, batch_size=1,
218
+ workers=0, allowlist=None, blocklist=None,
219
+ detail=1, paragraph=False, contrast_ths=0.1,
220
+ adjust_contrast=0.5):
221
+ """
222
+ Recognizes text from previously detected regions.
223
+
224
+ Args:
225
+ image_np (np.ndarray): Input image as a numpy array.
226
+ horizontal_list (list): List of rectangular regions [x_min, x_max, y_min, y_max].
227
+ free_list (list): List of free-form regions [[x1,y1],[x2,y2],[x3,y3],[x4,y4]].
228
+ Other parameters: Same as extract method.
229
+
230
+ Returns:
231
+ list: OCR results for the specified regions
232
+ """
233
+ self.setup()
234
+ return self.reader.recognize(
235
+ image_np,
236
+ horizontal_list=horizontal_list,
237
+ free_list=free_list,
238
+ decoder=decoder,
239
+ beamWidth=beam_width,
240
+ batch_size=batch_size,
241
+ workers=workers,
242
+ allowlist=allowlist,
243
+ blocklist=blocklist,
244
+ detail=detail,
245
+ paragraph=paragraph,
246
+ contrast_ths=contrast_ths,
247
+ adjust_contrast=adjust_contrast
248
+ )
@@ -0,0 +1,271 @@
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
+ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging_level)
13
+ self.logger = logging.getLogger('TextPostprocessor')
14
+
15
+ self.task_processors = {
16
+ "license_plate": self._process_license_plate,
17
+ "license_plate_india": self._process_license_plate_india,
18
+ "license_plate_us": self._process_license_plate_us,
19
+ "license_plate_eu": self._process_license_plate_eu,
20
+ "license_plate_qatar": self._process_license_plate_qatar,
21
+ }
22
+
23
+ self.char_substitutions = {
24
+ 'O': '0',
25
+ 'o': '0',
26
+ 'I': '1',
27
+ 'Z': '2',
28
+ 'A': '4',
29
+ 'L': '1',
30
+ 'AV': 'AV',
31
+ 'S': '5',
32
+ 'B': '8',
33
+ 'D': '0',
34
+ 'Q': '0',
35
+ 'G': '6',
36
+ 'T': '7'
37
+ }
38
+
39
+ def postprocess(self, texts, confidences, task=None, confidence_threshold=0.25, cleanup=True, region=None):
40
+ """
41
+ Postprocesses the extracted text by cleaning and filtering low-confidence results.
42
+ Applies task-specific processing if a task is specified.
43
+
44
+ Args:
45
+ texts (list): List of extracted text strings.
46
+ confidences (list): List of confidence scores corresponding to each text.
47
+ task (str): Specific task for customized postprocessing. Default is None.
48
+ confidence_threshold (float): Minimum confidence required to keep the text. Default is 0.5.
49
+ cleanup (bool): Whether to perform text cleanup.
50
+ region (str): Specific region for license plate processing ('india', 'us', 'eu', 'qatar'). Default is None.
51
+
52
+ Returns:
53
+ list: List of processed texts with corresponding confidence scores and validity flags.
54
+ """
55
+ results = []
56
+
57
+ for text, confidence in zip(texts, confidences):
58
+ if confidence < confidence_threshold:
59
+ self.logger.debug(f"Text '{text}' rejected: confidence {confidence} below threshold {confidence_threshold}")
60
+ results.append((None, confidence, False))
61
+ continue
62
+
63
+ if cleanup:
64
+ processed_text = self._clean_text(text)
65
+ else:
66
+ processed_text = text
67
+
68
+ if task and processed_text:
69
+ if task == "license_plate" and region:
70
+ region_task = f"license_plate_{region.lower()}"
71
+ if region_task in self.task_processors:
72
+ processed_text = self.task_processors[region_task](processed_text)
73
+ else:
74
+ processed_text = self.task_processors["license_plate"](processed_text)
75
+ self.logger.warning(f"Region '{region}' not supported, using generic license plate processor")
76
+ elif task in self.task_processors:
77
+ processed_text = self.task_processors[task](processed_text)
78
+ else:
79
+ self.logger.warning(f"Task '{task}' not supported, skipping task-specific processing")
80
+
81
+ if processed_text:
82
+ self.logger.debug(f"Text processed successfully: '{text}' -> '{processed_text}'")
83
+ results.append((processed_text, confidence, True))
84
+ else:
85
+ self.logger.debug(f"Text '{text}' rejected during processing")
86
+ results.append((None, confidence, False))
87
+
88
+ return results
89
+
90
+ def _clean_text(self, text):
91
+ """
92
+ Basic text cleaning operations.
93
+
94
+ Args:
95
+ text (str): Text to clean.
96
+
97
+ Returns:
98
+ str: Cleaned text.
99
+ """
100
+ clean_text = text.strip()
101
+ clean_text = ''.join(char for char in clean_text if char.isprintable())
102
+ clean_text = ' '.join(clean_text.split())
103
+
104
+ return clean_text
105
+
106
+ def _process_license_plate(self, text):
107
+ """
108
+ Generic license plate processor that respects the specified region.
109
+
110
+ Args:
111
+ text (str): License plate text to process.
112
+
113
+ Returns:
114
+ str: Processed license plate text or None if invalid.
115
+ """
116
+ plate_text = text.upper()
117
+ plate_text = ''.join(plate_text.split())
118
+
119
+ if self.region and self.region.lower() == 'qatar':
120
+ return self._process_license_plate_qatar(plate_text)
121
+ elif self.region and self.region.lower() == 'india':
122
+ return self._process_license_plate_india(plate_text)
123
+ elif self.region and self.region.lower() == 'us':
124
+ return self._process_license_plate_us(plate_text)
125
+ elif self.region and self.region.lower() == 'eu':
126
+ return self._process_license_plate_eu(plate_text)
127
+ else:
128
+ if re.match(r'^[A-Z]{2}\d{1,2}[A-Z]{1,2}\d{4}$', plate_text):
129
+ return self._process_license_plate_india(plate_text)
130
+ elif re.match(r'^[A-Z0-9]{1,8}$', plate_text) and len(plate_text) <= 8:
131
+ return self._process_license_plate_us(plate_text)
132
+ elif re.match(r'^[A-Z]{1,3}[-\s]?[A-Z0-9]{1,4}[-\s]?[A-Z0-9]{1,3}$', plate_text):
133
+ return self._process_license_plate_eu(plate_text)
134
+ elif re.match(r'^\d{1,6}\s*[A-Z]+?$', plate_text):
135
+ return self._process_license_plate_qatar(plate_text)
136
+ else:
137
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
138
+ if 4 <= len(plate_text) <= 10:
139
+ return plate_text
140
+
141
+ self.logger.warning(f"Could not identify license plate format: '{text}'")
142
+ return None
143
+
144
+ def _process_license_plate_india(self, text):
145
+ plate_text = text.upper().replace(" ", "")
146
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
147
+ for old, new in self.char_substitutions.items():
148
+ plate_text = plate_text.replace(old, new)
149
+
150
+ if len(plate_text) >= 7:
151
+ state_code = plate_text[:2]
152
+ rest = plate_text[2:]
153
+ match = re.match(r'^(\d{1,2})[ -]?([A-Z]{1,2})[ -]?(\d{4})$', rest)
154
+ 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']:
155
+ district, series, number = match.groups()
156
+ formatted_plate = f"{state_code}{district}{series}{number}"
157
+ self.logger.info(f"Processed Indian license plate: '{text}' -> '{formatted_plate}'")
158
+ return formatted_plate
159
+ self.logger.warning(f"Invalid Indian license plate format: '{text}'")
160
+ return None
161
+
162
+ def _process_license_plate_us(self, text):
163
+ plate_text = text.upper()
164
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
165
+
166
+ for old, new in self.char_substitutions.items():
167
+ plate_text = plate_text.replace(old, new)
168
+
169
+ if re.match(r'^[A-Z]{3}\d{4}$', plate_text) or re.match(r'^\d{3}[A-Z]{4}$', plate_text):
170
+ self.logger.info(f"Processed US license plate (standard format): '{text}' -> '{plate_text}'")
171
+ return plate_text
172
+ if 2 <= len(plate_text) <= 8 and re.match(r'^[A-Z0-9]+$', plate_text):
173
+ self.logger.info(f"Processed US license plate (vanity/other format): '{text}' -> '{plate_text}'")
174
+ return plate_text
175
+
176
+ self.logger.warning(f"Invalid US license plate format: '{text}'")
177
+ return None
178
+
179
+ def _process_license_plate_eu(self, text):
180
+ plate_text = text.upper()
181
+ plate_text = ''.join(char for char in plate_text if char.isalnum() or char == '-')
182
+
183
+ if '-' not in plate_text and len(plate_text) > 3:
184
+ for i in range(1, 4):
185
+ if i < len(plate_text) and plate_text[i].isdigit() and plate_text[i-1].isalpha():
186
+ plate_text = plate_text[:i] + '-' + plate_text[i:]
187
+ break
188
+
189
+ for old, new in self.char_substitutions.items():
190
+ plate_text = plate_text.replace(old, new)
191
+
192
+ if re.match(r'^[A-Z]{1,3}-[A-Z]{1,2}\d{1,4}$', plate_text):
193
+ self.logger.info(f"Processed German license plate: '{text}' -> '{plate_text}'")
194
+ return plate_text
195
+ if re.match(r'^[A-Z]{2}\d{2}[A-Z]{3}$', plate_text):
196
+ self.logger.info(f"Processed UK license plate: '{text}' -> '{plate_text}'")
197
+ return plate_text
198
+ 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):
199
+ self.logger.info(f"Processed French license plate: '{text}' -> '{plate_text}'")
200
+ return plate_text
201
+ if re.match(r'^[A-Z]{2}\d{3}[A-Z]{2}$', plate_text):
202
+ self.logger.info(f"Processed Italian license plate: '{text}' -> '{plate_text}'")
203
+ return plate_text
204
+ if re.match(r'^\d{4}[BCDFGHJKLMNPRSTVWXYZ]{3}$', plate_text):
205
+ self.logger.info(f"Processed Spanish license plate: '{text}' -> '{plate_text}'")
206
+ return plate_text
207
+ if re.search(r'[A-Z]', plate_text) and re.search(r'\d', plate_text) and 4 <= len(plate_text) <= 10:
208
+ self.logger.info(f"Processed generic European license plate: '{text}' -> '{plate_text}'")
209
+ return plate_text
210
+
211
+ self.logger.warning(f"Invalid European license plate format: '{text}'")
212
+ return None
213
+
214
+ def _process_license_plate_qatar(self, text):
215
+ """
216
+ Process Qatar license plate text by converting Arabic numerals to Latin and keeping only digits.
217
+
218
+ Args:
219
+ text (str): License plate text to process.
220
+
221
+ Returns:
222
+ str: Processed license plate text or None if invalid.
223
+ """
224
+ # Check for Unicode escape sequences (e.g., \u0664)
225
+ if r'\u' in str(text):
226
+ self.logger.warning(f"Invalid Qatar license plate format: '{text}' contains Unicode escape sequence")
227
+ return None
228
+
229
+ # Define Arabic to Latin numeral mapping
230
+ arabic_to_latin = str.maketrans('٠١٢٣٤٥٦٧٨٩', '0123456789')
231
+
232
+ # Convert Arabic numerals to Latin and keep only alphanumeric characters
233
+ plate_text = text.translate(arabic_to_latin)
234
+ plate_text = ''.join(char for char in plate_text if char.isalnum())
235
+
236
+ # Apply character substitutions for common OCR errors
237
+ for old, new in self.char_substitutions.items():
238
+ plate_text = plate_text.replace(old, new)
239
+
240
+ # Keep only digits for Qatar license plates
241
+ plate_text = ''.join(char for char in plate_text if char.isdigit())
242
+
243
+ # Validate: Ensure the text is 1 to 6 digits
244
+ if re.match(r'^\d{1,6}$', plate_text):
245
+ self.logger.info(f"Processed Qatar license plate: '{text}' -> '{plate_text}'")
246
+ return plate_text
247
+
248
+ self.logger.warning(f"Invalid Qatar license plate format: '{text}'")
249
+ return None
250
+
251
+ def _string_similarity(self, s1, s2):
252
+ if len(s1) > len(s2):
253
+ s1, s2 = s2, s1
254
+
255
+ distances = range(len(s1) + 1)
256
+ for i2, c2 in enumerate(s2):
257
+ distances_ = [i2+1]
258
+ for i1, c1 in enumerate(s1):
259
+ if c1 == c2:
260
+ distances_.append(distances[i1])
261
+ else:
262
+ distances_.append(1 + min((distances[i1], distances[i1 + 1], distances_[-1])))
263
+ distances = distances_
264
+
265
+ max_len = max(len(s1), len(s2))
266
+ similarity = 1 - (distances[-1] / max_len if max_len > 0 else 0)
267
+ return similarity
268
+
269
+ def add_task_processor(self, task_name, processor_function):
270
+ self.task_processors[task_name] = processor_function
271
+ 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