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,1036 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>🎯 Boundary Drawing Tool</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ margin: 0;
11
+ padding: 20px;
12
+ background-color: #f8f9fa;
13
+ color: #333;
14
+ }
15
+ .container {
16
+ max-width: 1400px;
17
+ margin: 0 auto;
18
+ }
19
+ h1 {
20
+ color: #2c3e50;
21
+ text-align: center;
22
+ margin-bottom: 30px;
23
+ font-size: 28px;
24
+ }
25
+ .upload-section {
26
+ background: white;
27
+ border-radius: 8px;
28
+ padding: 30px;
29
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
30
+ margin-bottom: 30px;
31
+ text-align: center;
32
+ }
33
+ .upload-area {
34
+ border: 3px dashed #007bff;
35
+ border-radius: 8px;
36
+ padding: 40px;
37
+ background: #f8f9ff;
38
+ cursor: pointer;
39
+ transition: all 0.3s ease;
40
+ }
41
+ .upload-area:hover {
42
+ background: #e3f2fd;
43
+ border-color: #0056b3;
44
+ }
45
+ .upload-area.dragover {
46
+ background: #bbdefb;
47
+ border-color: #1976d2;
48
+ }
49
+ .upload-icon {
50
+ font-size: 48px;
51
+ color: #007bff;
52
+ margin-bottom: 15px;
53
+ }
54
+ .main-content {
55
+ display: grid;
56
+ grid-template-columns: 300px 1fr;
57
+ gap: 20px;
58
+ min-height: calc(100vh - 200px);
59
+ }
60
+ .sidebar {
61
+ background: white;
62
+ border-radius: 8px;
63
+ padding: 20px;
64
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
65
+ height: fit-content;
66
+ position: sticky;
67
+ top: 20px;
68
+ }
69
+ .image-container {
70
+ background: white;
71
+ border-radius: 8px;
72
+ padding: 20px;
73
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
74
+ position: relative;
75
+ overflow: auto;
76
+ }
77
+ .image-wrapper {
78
+ position: relative;
79
+ display: inline-block;
80
+ border: 2px solid #ddd;
81
+ border-radius: 4px;
82
+ overflow: hidden;
83
+ }
84
+ img {
85
+ display: block;
86
+ max-width: 100%;
87
+ height: auto;
88
+ }
89
+ canvas {
90
+ position: absolute;
91
+ top: 0;
92
+ left: 0;
93
+ cursor: crosshair;
94
+ pointer-events: auto;
95
+ }
96
+ .control-section {
97
+ margin-bottom: 25px;
98
+ padding-bottom: 20px;
99
+ border-bottom: 1px solid #eee;
100
+ }
101
+ .control-section:last-child {
102
+ border-bottom: none;
103
+ }
104
+ .control-section h3 {
105
+ margin: 0 0 15px 0;
106
+ color: #34495e;
107
+ font-size: 16px;
108
+ font-weight: 600;
109
+ }
110
+ .zone-types {
111
+ display: grid;
112
+ gap: 8px;
113
+ margin-bottom: 15px;
114
+ }
115
+ .zone-btn {
116
+ padding: 10px 15px;
117
+ border: 2px solid transparent;
118
+ border-radius: 6px;
119
+ cursor: pointer;
120
+ font-size: 14px;
121
+ font-weight: 500;
122
+ text-align: center;
123
+ transition: all 0.2s ease;
124
+ background: #f8f9fa;
125
+ color: #495057;
126
+ }
127
+ .zone-btn:hover {
128
+ transform: translateY(-1px);
129
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
130
+ }
131
+ .zone-btn.active {
132
+ background: #007bff;
133
+ color: white;
134
+ border-color: #0056b3;
135
+ }
136
+ .zone-btn.queue { background: #28a745; color: white; }
137
+ .zone-btn.staff { background: #17a2b8; color: white; }
138
+ .zone-btn.entry { background: #ffc107; color: #212529; }
139
+ .zone-btn.exit { background: #dc3545; color: white; }
140
+ .zone-btn.restricted { background: #6f42c1; color: white; }
141
+ .zone-btn.waiting { background: #fd7e14; color: white; }
142
+ .zone-btn.service { background: #20c997; color: white; }
143
+ .zone-btn.security { background: #495057; color: white; }
144
+
145
+ .drawing-controls {
146
+ display: grid;
147
+ gap: 8px;
148
+ }
149
+ .control-btn {
150
+ padding: 8px 12px;
151
+ border: 1px solid #ddd;
152
+ border-radius: 4px;
153
+ background: white;
154
+ cursor: pointer;
155
+ font-size: 13px;
156
+ transition: all 0.2s ease;
157
+ }
158
+ .control-btn:hover {
159
+ background: #f8f9fa;
160
+ border-color: #adb5bd;
161
+ }
162
+ .control-btn.primary {
163
+ background: #007bff;
164
+ color: white;
165
+ border-color: #0056b3;
166
+ }
167
+ .control-btn.danger {
168
+ background: #dc3545;
169
+ color: white;
170
+ border-color: #c82333;
171
+ }
172
+ .control-btn.success {
173
+ background: #28a745;
174
+ color: white;
175
+ border-color: #1e7e34;
176
+ }
177
+
178
+ .coordinates-display {
179
+ font-family: 'Courier New', monospace;
180
+ font-size: 12px;
181
+ color: #666;
182
+ background: #f8f9fa;
183
+ padding: 8px;
184
+ border-radius: 4px;
185
+ margin-bottom: 10px;
186
+ min-height: 20px;
187
+ }
188
+
189
+ .zones-list {
190
+ max-height: 300px;
191
+ overflow-y: auto;
192
+ border: 1px solid #ddd;
193
+ border-radius: 4px;
194
+ background: #f8f9fa;
195
+ }
196
+ .zone-item {
197
+ padding: 10px;
198
+ border-bottom: 1px solid #ddd;
199
+ display: flex;
200
+ justify-content: space-between;
201
+ align-items: center;
202
+ background: white;
203
+ margin-bottom: 1px;
204
+ }
205
+ .zone-item:last-child {
206
+ border-bottom: none;
207
+ }
208
+ .zone-tag {
209
+ padding: 2px 8px;
210
+ border-radius: 3px;
211
+ font-size: 11px;
212
+ font-weight: 600;
213
+ color: white;
214
+ }
215
+ .zone-actions {
216
+ display: flex;
217
+ gap: 5px;
218
+ }
219
+ .zone-actions button {
220
+ padding: 2px 6px;
221
+ border: none;
222
+ border-radius: 3px;
223
+ cursor: pointer;
224
+ font-size: 11px;
225
+ }
226
+
227
+ .code-output {
228
+ background: #2d3748;
229
+ color: #e2e8f0;
230
+ padding: 15px;
231
+ border-radius: 6px;
232
+ font-family: 'Courier New', monospace;
233
+ font-size: 12px;
234
+ white-space: pre-wrap;
235
+ max-height: 200px;
236
+ overflow-y: auto;
237
+ margin-top: 10px;
238
+ border: 1px solid #4a5568;
239
+ }
240
+
241
+ .current-mouse {
242
+ position: fixed;
243
+ bottom: 20px;
244
+ right: 20px;
245
+ background: rgba(0,0,0,0.8);
246
+ color: white;
247
+ padding: 8px 12px;
248
+ border-radius: 4px;
249
+ font-family: monospace;
250
+ font-size: 12px;
251
+ pointer-events: none;
252
+ z-index: 1000;
253
+ }
254
+
255
+ .mode-indicator {
256
+ background: #e3f2fd;
257
+ border: 1px solid #2196f3;
258
+ border-radius: 4px;
259
+ padding: 8px 12px;
260
+ margin-bottom: 15px;
261
+ text-align: center;
262
+ font-weight: 500;
263
+ color: #1976d2;
264
+ }
265
+
266
+ .instructions {
267
+ background: #f0f8f0;
268
+ border: 1px solid #4caf50;
269
+ border-radius: 4px;
270
+ padding: 12px;
271
+ margin-bottom: 20px;
272
+ font-size: 13px;
273
+ line-height: 1.4;
274
+ }
275
+
276
+ .custom-tag-input {
277
+ display: flex;
278
+ gap: 5px;
279
+ margin-top: 10px;
280
+ }
281
+ .custom-tag-input input {
282
+ flex: 1;
283
+ padding: 6px 8px;
284
+ border: 1px solid #ddd;
285
+ border-radius: 4px;
286
+ font-size: 12px;
287
+ }
288
+ .custom-tag-input button {
289
+ padding: 6px 10px;
290
+ background: #28a745;
291
+ color: white;
292
+ border: none;
293
+ border-radius: 4px;
294
+ cursor: pointer;
295
+ font-size: 12px;
296
+ }
297
+
298
+ .hidden {
299
+ display: none !important;
300
+ }
301
+
302
+ .loading {
303
+ text-align: center;
304
+ padding: 40px;
305
+ color: #666;
306
+ }
307
+
308
+ .error {
309
+ background: #f8d7da;
310
+ color: #721c24;
311
+ padding: 15px;
312
+ border-radius: 4px;
313
+ border: 1px solid #f5c6cb;
314
+ margin: 15px 0;
315
+ }
316
+ </style>
317
+ </head>
318
+ <body>
319
+ <div class="container">
320
+ <h1>🎯 Boundary Drawing Tool</h1>
321
+
322
+ <div id="uploadSection" class="upload-section">
323
+ <div class="upload-area" onclick="document.getElementById('fileInput').click()">
324
+ <div class="upload-icon">📁</div>
325
+ <h3>Select Image or Video File</h3>
326
+ <p>Click here to select a file or drag and drop</p>
327
+ <p style="color: #666; font-size: 14px;">
328
+ Supported formats: JPG, PNG, MP4, AVI, MOV, etc.
329
+ </p>
330
+ </div>
331
+ <input type="file" id="fileInput" accept="image/*,video/*" style="display: none;">
332
+ </div>
333
+
334
+ <div id="mainContent" class="main-content hidden">
335
+ <div class="sidebar">
336
+ <div class="instructions">
337
+ <strong>Instructions:</strong><br>
338
+ 1. Select a zone type<br>
339
+ 2. Click on image to add points<br>
340
+ 3. Complete polygon/line<br>
341
+ 4. Copy generated code
342
+ </div>
343
+
344
+ <div class="mode-indicator" id="modeIndicator">
345
+ Select a zone type to start
346
+ </div>
347
+
348
+ <div class="control-section">
349
+ <h3>Zone Types</h3>
350
+ <div class="zone-types">
351
+ <div class="zone-btn queue" onclick="setZoneType('queue')">🏃 Queue Area</div>
352
+ <div class="zone-btn staff" onclick="setZoneType('staff')">👥 Staff Area</div>
353
+ <div class="zone-btn entry" onclick="setZoneType('entry')">🚪 Entry Zone</div>
354
+ <div class="zone-btn exit" onclick="setZoneType('exit')">🚶 Exit Zone</div>
355
+ <div class="zone-btn restricted" onclick="setZoneType('restricted')">🚫 Restricted</div>
356
+ <div class="zone-btn waiting" onclick="setZoneType('waiting')">⏰ Waiting Area</div>
357
+ <div class="zone-btn service" onclick="setZoneType('service')">🛎️ Service Area</div>
358
+ <div class="zone-btn security" onclick="setZoneType('security')">🔒 Security Zone</div>
359
+ </div>
360
+
361
+ <div class="custom-tag-input">
362
+ <input type="text" id="customTagInput" placeholder="Custom tag...">
363
+ <button onclick="setCustomZoneType()">Add</button>
364
+ </div>
365
+ </div>
366
+
367
+ <div class="control-section">
368
+ <h3>Drawing Mode</h3>
369
+ <div class="drawing-controls">
370
+ <button class="control-btn primary" onclick="setDrawingMode('polygon')" id="polygonBtn">📐 Polygon</button>
371
+ <button class="control-btn" onclick="setDrawingMode('line')" id="lineBtn">📏 Line</button>
372
+ </div>
373
+ </div>
374
+
375
+ <div class="control-section">
376
+ <h3>Controls</h3>
377
+ <div class="drawing-controls">
378
+ <button class="control-btn primary" onclick="completeCurrentZone()">✅ Complete Zone</button>
379
+ <button class="control-btn" onclick="undoLastPoint()">↶ Undo Point</button>
380
+ <button class="control-btn danger" onclick="cancelCurrentZone()">❌ Cancel Zone</button>
381
+ <button class="control-btn" onclick="clearAll()">🗑️ Clear All</button>
382
+ </div>
383
+ </div>
384
+
385
+ <div class="control-section">
386
+ <h3>Current Position</h3>
387
+ <div class="coordinates-display" id="currentCoords">
388
+ Move mouse over image
389
+ </div>
390
+ </div>
391
+
392
+ <div class="control-section">
393
+ <h3>Export</h3>
394
+ <div class="drawing-controls">
395
+ <button class="control-btn success" onclick="generateCode()">📋 Generate Code</button>
396
+ <button class="control-btn" onclick="saveConfiguration()">💾 Save Config</button>
397
+ <button class="control-btn" onclick="loadConfiguration()">📁 Load Config</button>
398
+ </div>
399
+ </div>
400
+ </div>
401
+
402
+ <div class="image-container">
403
+ <div class="image-wrapper">
404
+ <img id="referenceImage" alt="Reference Image" style="display: none;">
405
+ <canvas id="drawingCanvas"></canvas>
406
+ </div>
407
+
408
+ <div class="control-section">
409
+ <h3>Defined Zones</h3>
410
+ <div class="zones-list" id="zonesList">
411
+ <div style="padding: 20px; text-align: center; color: #666;">
412
+ No zones defined yet
413
+ </div>
414
+ </div>
415
+ </div>
416
+
417
+ <div class="control-section">
418
+ <h3>Generated Code</h3>
419
+ <div class="code-output" id="codeOutput">
420
+ # No zones defined yet
421
+ zones = {}
422
+ </div>
423
+ <button class="control-btn success" onclick="copyCode()" style="margin-top: 10px;">📋 Copy Code</button>
424
+ </div>
425
+ </div>
426
+ </div>
427
+ </div>
428
+
429
+ <div class="current-mouse" id="mouseTracker">x: 0, y: 0</div>
430
+
431
+ <input type="file" id="configInput" accept=".json" style="display: none;" onchange="handleConfigLoad(event)">
432
+
433
+ <script>
434
+ // Global state
435
+ let currentZoneType = null;
436
+ let currentDrawingMode = 'polygon';
437
+ let currentPoints = [];
438
+ let completedZones = [];
439
+ let isDrawing = false;
440
+ let mousePos = { x: 0, y: 0 };
441
+ let currentFile = null;
442
+
443
+ // Canvas and image elements
444
+ const canvas = document.getElementById('drawingCanvas');
445
+ const ctx = canvas.getContext('2d');
446
+ const img = document.getElementById('referenceImage');
447
+ const modeIndicator = document.getElementById('modeIndicator');
448
+ const currentCoords = document.getElementById('currentCoords');
449
+ const mouseTracker = document.getElementById('mouseTracker');
450
+ const zonesList = document.getElementById('zonesList');
451
+ const codeOutput = document.getElementById('codeOutput');
452
+ const uploadSection = document.getElementById('uploadSection');
453
+ const mainContent = document.getElementById('mainContent');
454
+ const fileInput = document.getElementById('fileInput');
455
+
456
+ // Zone type colors
457
+ const zoneColors = {
458
+ 'queue': '#28a745',
459
+ 'staff': '#17a2b8',
460
+ 'entry': '#ffc107',
461
+ 'exit': '#dc3545',
462
+ 'restricted': '#6f42c1',
463
+ 'waiting': '#fd7e14',
464
+ 'service': '#20c997',
465
+ 'security': '#495057'
466
+ };
467
+
468
+ // File upload handling
469
+ fileInput.addEventListener('change', handleFileSelect);
470
+
471
+ // Drag and drop handling
472
+ const uploadArea = document.querySelector('.upload-area');
473
+ uploadArea.addEventListener('dragover', (e) => {
474
+ e.preventDefault();
475
+ uploadArea.classList.add('dragover');
476
+ });
477
+
478
+ uploadArea.addEventListener('dragleave', (e) => {
479
+ e.preventDefault();
480
+ uploadArea.classList.remove('dragover');
481
+ });
482
+
483
+ uploadArea.addEventListener('drop', (e) => {
484
+ e.preventDefault();
485
+ uploadArea.classList.remove('dragover');
486
+ const files = e.dataTransfer.files;
487
+ if (files.length > 0) {
488
+ handleFile(files[0]);
489
+ }
490
+ });
491
+
492
+ function handleFileSelect(event) {
493
+ const file = event.target.files[0];
494
+ if (file) {
495
+ handleFile(file);
496
+ }
497
+ }
498
+
499
+ function handleFile(file) {
500
+ currentFile = file;
501
+ const fileType = file.type;
502
+
503
+ if (fileType.startsWith('image/')) {
504
+ loadImage(file);
505
+ } else if (fileType.startsWith('video/')) {
506
+ extractFirstFrame(file);
507
+ } else {
508
+ showError('Unsupported file format. Please select an image or video file.');
509
+ }
510
+ }
511
+
512
+ function loadImage(file) {
513
+ const reader = new FileReader();
514
+ reader.onload = function(e) {
515
+ img.src = e.target.result;
516
+ img.onload = function() {
517
+ setupCanvas();
518
+ showMainContent();
519
+ };
520
+ };
521
+ reader.readAsDataURL(file);
522
+ }
523
+
524
+ function extractFirstFrame(file) {
525
+ const video = document.createElement('video');
526
+ video.muted = true;
527
+ video.currentTime = 0;
528
+
529
+ video.onloadeddata = function() {
530
+ const canvas = document.createElement('canvas');
531
+ const ctx = canvas.getContext('2d');
532
+
533
+ canvas.width = video.videoWidth;
534
+ canvas.height = video.videoHeight;
535
+
536
+ ctx.drawImage(video, 0, 0);
537
+
538
+ const dataURL = canvas.toDataURL('image/jpeg');
539
+ img.src = dataURL;
540
+ img.onload = function() {
541
+ setupCanvas();
542
+ showMainContent();
543
+ };
544
+
545
+ URL.revokeObjectURL(video.src);
546
+ };
547
+
548
+ video.onerror = function() {
549
+ showError('Error loading video file. Please try a different format.');
550
+ URL.revokeObjectURL(video.src);
551
+ };
552
+
553
+ video.src = URL.createObjectURL(file);
554
+ }
555
+
556
+ function showMainContent() {
557
+ uploadSection.classList.add('hidden');
558
+ mainContent.classList.remove('hidden');
559
+ }
560
+
561
+ function showError(message) {
562
+ const errorDiv = document.createElement('div');
563
+ errorDiv.className = 'error';
564
+ errorDiv.textContent = message;
565
+ uploadSection.appendChild(errorDiv);
566
+ setTimeout(() => errorDiv.remove(), 5000);
567
+ }
568
+
569
+ function setupCanvas() {
570
+ canvas.width = img.naturalWidth;
571
+ canvas.height = img.naturalHeight;
572
+ canvas.style.width = img.clientWidth + 'px';
573
+ canvas.style.height = img.clientHeight + 'px';
574
+ img.style.display = 'block';
575
+ redrawCanvas();
576
+ }
577
+
578
+ // Mouse event handlers
579
+ canvas.addEventListener('mousemove', function(e) {
580
+ updateMousePosition(e);
581
+ });
582
+
583
+ canvas.addEventListener('click', function(e) {
584
+ if (!currentZoneType) {
585
+ alert('Please select a zone type first!');
586
+ return;
587
+ }
588
+ addPoint(e);
589
+ });
590
+
591
+ canvas.addEventListener('contextmenu', function(e) {
592
+ e.preventDefault();
593
+ if (currentPoints.length > 0) {
594
+ completeCurrentZone();
595
+ }
596
+ });
597
+
598
+ function updateMousePosition(e) {
599
+ const rect = canvas.getBoundingClientRect();
600
+ const scaleX = canvas.width / rect.width;
601
+ const scaleY = canvas.height / rect.height;
602
+
603
+ mousePos.x = Math.round((e.clientX - rect.left) * scaleX);
604
+ mousePos.y = Math.round((e.clientY - rect.top) * scaleY);
605
+
606
+ currentCoords.textContent = `x: ${mousePos.x}, y: ${mousePos.y}`;
607
+ mouseTracker.textContent = `x: ${mousePos.x}, y: ${mousePos.y}`;
608
+
609
+ redrawCanvas();
610
+ }
611
+
612
+ function addPoint(e) {
613
+ const rect = canvas.getBoundingClientRect();
614
+ const scaleX = canvas.width / rect.width;
615
+ const scaleY = canvas.height / rect.height;
616
+
617
+ const x = Math.round((e.clientX - rect.left) * scaleX);
618
+ const y = Math.round((e.clientY - rect.top) * scaleY);
619
+
620
+ currentPoints.push([x, y]);
621
+ isDrawing = true;
622
+
623
+ updateModeIndicator();
624
+ redrawCanvas();
625
+
626
+ // Auto-complete line when 2 points are added
627
+ if (currentDrawingMode === 'line' && currentPoints.length === 2) {
628
+ completeCurrentZone();
629
+ }
630
+ }
631
+
632
+ function setZoneType(type) {
633
+ currentZoneType = type;
634
+
635
+ // Update UI
636
+ document.querySelectorAll('.zone-btn').forEach(btn => {
637
+ btn.classList.remove('active');
638
+ });
639
+ event.target.classList.add('active');
640
+
641
+ updateModeIndicator();
642
+ }
643
+
644
+ function setCustomZoneType() {
645
+ const input = document.getElementById('customTagInput');
646
+ const customType = input.value.trim();
647
+
648
+ if (customType) {
649
+ currentZoneType = customType;
650
+ input.value = '';
651
+
652
+ // Remove active class from preset buttons
653
+ document.querySelectorAll('.zone-btn').forEach(btn => {
654
+ btn.classList.remove('active');
655
+ });
656
+
657
+ updateModeIndicator();
658
+ }
659
+ }
660
+
661
+ function setDrawingMode(mode) {
662
+ currentDrawingMode = mode;
663
+
664
+ // Update UI
665
+ document.getElementById('polygonBtn').classList.remove('primary');
666
+ document.getElementById('lineBtn').classList.remove('primary');
667
+ document.getElementById(mode + 'Btn').classList.add('primary');
668
+
669
+ updateModeIndicator();
670
+ }
671
+
672
+ function updateModeIndicator() {
673
+ if (!currentZoneType) {
674
+ modeIndicator.textContent = 'Select a zone type to start';
675
+ modeIndicator.style.background = '#f8f9fa';
676
+ modeIndicator.style.borderColor = '#ddd';
677
+ modeIndicator.style.color = '#666';
678
+ return;
679
+ }
680
+
681
+ let modeText = `Drawing ${currentDrawingMode} for "${currentZoneType}" zone`;
682
+
683
+ if (isDrawing) {
684
+ const pointsNeeded = currentDrawingMode === 'line' ? 2 : 3;
685
+ const remaining = Math.max(0, pointsNeeded - currentPoints.length);
686
+ modeText += ` (${currentPoints.length} points, need ${remaining} more)`;
687
+ }
688
+
689
+ modeIndicator.textContent = modeText;
690
+ modeIndicator.style.background = '#e3f2fd';
691
+ modeIndicator.style.borderColor = '#2196f3';
692
+ modeIndicator.style.color = '#1976d2';
693
+ }
694
+
695
+ function completeCurrentZone() {
696
+ if (!currentZoneType || currentPoints.length === 0) return;
697
+
698
+ const minPoints = currentDrawingMode === 'line' ? 2 : 3;
699
+ if (currentPoints.length < minPoints) {
700
+ alert(`Need at least ${minPoints} points for a ${currentDrawingMode}!`);
701
+ return;
702
+ }
703
+
704
+ // Create zone object
705
+ const zone = {
706
+ type: currentZoneType,
707
+ mode: currentDrawingMode,
708
+ points: [...currentPoints],
709
+ color: zoneColors[currentZoneType] || '#333333',
710
+ id: 'zone_' + Date.now()
711
+ };
712
+
713
+ completedZones.push(zone);
714
+
715
+ // Reset current drawing
716
+ currentPoints = [];
717
+ isDrawing = false;
718
+
719
+ updateModeIndicator();
720
+ updateZonesList();
721
+ generateCode();
722
+ redrawCanvas();
723
+ }
724
+
725
+ function undoLastPoint() {
726
+ if (currentPoints.length > 0) {
727
+ currentPoints.pop();
728
+ updateModeIndicator();
729
+ redrawCanvas();
730
+ }
731
+ }
732
+
733
+ function cancelCurrentZone() {
734
+ currentPoints = [];
735
+ isDrawing = false;
736
+ updateModeIndicator();
737
+ redrawCanvas();
738
+ }
739
+
740
+ function clearAll() {
741
+ if (confirm('Clear all zones? This cannot be undone.')) {
742
+ currentPoints = [];
743
+ completedZones = [];
744
+ isDrawing = false;
745
+ updateModeIndicator();
746
+ updateZonesList();
747
+ generateCode();
748
+ redrawCanvas();
749
+ }
750
+ }
751
+
752
+ function deleteZone(zoneId) {
753
+ completedZones = completedZones.filter(zone => zone.id !== zoneId);
754
+ updateZonesList();
755
+ generateCode();
756
+ redrawCanvas();
757
+ }
758
+
759
+ function redrawCanvas() {
760
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
761
+
762
+ // Draw completed zones
763
+ completedZones.forEach(zone => {
764
+ drawZone(zone);
765
+ });
766
+
767
+ // Draw current drawing
768
+ if (currentPoints.length > 0) {
769
+ const color = zoneColors[currentZoneType] || '#333333';
770
+ drawCurrentDrawing(color);
771
+ }
772
+
773
+ // Draw mouse cursor
774
+ if (mousePos.x > 0 && mousePos.y > 0 && isDrawing) {
775
+ ctx.beginPath();
776
+ ctx.arc(mousePos.x, mousePos.y, 6, 0, Math.PI * 2);
777
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
778
+ ctx.fill();
779
+ ctx.strokeStyle = '#333';
780
+ ctx.lineWidth = 2;
781
+ ctx.stroke();
782
+ }
783
+ }
784
+
785
+ function drawZone(zone) {
786
+ if (zone.points.length === 0) return;
787
+
788
+ ctx.strokeStyle = zone.color;
789
+ ctx.fillStyle = zone.color + '40'; // Add transparency
790
+ ctx.lineWidth = 3;
791
+
792
+ if (zone.mode === 'line') {
793
+ // Draw line
794
+ ctx.beginPath();
795
+ ctx.moveTo(zone.points[0][0], zone.points[0][1]);
796
+ ctx.lineTo(zone.points[1][0], zone.points[1][1]);
797
+ ctx.stroke();
798
+ } else {
799
+ // Draw polygon
800
+ ctx.beginPath();
801
+ ctx.moveTo(zone.points[0][0], zone.points[0][1]);
802
+ for (let i = 1; i < zone.points.length; i++) {
803
+ ctx.lineTo(zone.points[i][0], zone.points[i][1]);
804
+ }
805
+ ctx.closePath();
806
+ ctx.fill();
807
+ ctx.stroke();
808
+ }
809
+
810
+ // Draw points
811
+ zone.points.forEach((point, index) => {
812
+ ctx.beginPath();
813
+ ctx.arc(point[0], point[1], 5, 0, Math.PI * 2);
814
+ ctx.fillStyle = '#fff';
815
+ ctx.fill();
816
+ ctx.strokeStyle = zone.color;
817
+ ctx.lineWidth = 2;
818
+ ctx.stroke();
819
+
820
+ // Draw point number
821
+ ctx.fillStyle = '#333';
822
+ ctx.font = '12px Arial';
823
+ ctx.textAlign = 'center';
824
+ ctx.fillText(index + 1, point[0], point[1] - 10);
825
+ });
826
+
827
+ // Draw zone label
828
+ if (zone.points.length > 0) {
829
+ const centerX = zone.points.reduce((sum, p) => sum + p[0], 0) / zone.points.length;
830
+ const centerY = zone.points.reduce((sum, p) => sum + p[1], 0) / zone.points.length;
831
+
832
+ ctx.fillStyle = zone.color;
833
+ ctx.font = 'bold 14px Arial';
834
+ ctx.textAlign = 'center';
835
+ ctx.fillText(zone.type.toUpperCase(), centerX, centerY);
836
+ }
837
+ }
838
+
839
+ function drawCurrentDrawing(color) {
840
+ if (currentPoints.length === 0) return;
841
+
842
+ ctx.strokeStyle = color;
843
+ ctx.fillStyle = color + '20';
844
+ ctx.lineWidth = 2;
845
+ ctx.setLineDash([5, 5]);
846
+
847
+ if (currentDrawingMode === 'line' && currentPoints.length >= 2) {
848
+ // Draw line
849
+ ctx.beginPath();
850
+ ctx.moveTo(currentPoints[0][0], currentPoints[0][1]);
851
+ ctx.lineTo(currentPoints[1][0], currentPoints[1][1]);
852
+ ctx.stroke();
853
+ } else if (currentDrawingMode === 'polygon' && currentPoints.length >= 2) {
854
+ // Draw polygon outline
855
+ ctx.beginPath();
856
+ ctx.moveTo(currentPoints[0][0], currentPoints[0][1]);
857
+ for (let i = 1; i < currentPoints.length; i++) {
858
+ ctx.lineTo(currentPoints[i][0], currentPoints[i][1]);
859
+ }
860
+ ctx.stroke();
861
+
862
+ // Draw line to mouse position
863
+ if (mousePos.x > 0 && mousePos.y > 0) {
864
+ ctx.lineTo(mousePos.x, mousePos.y);
865
+ ctx.stroke();
866
+ }
867
+ }
868
+
869
+ ctx.setLineDash([]);
870
+
871
+ // Draw current points
872
+ currentPoints.forEach((point, index) => {
873
+ ctx.beginPath();
874
+ ctx.arc(point[0], point[1], 4, 0, Math.PI * 2);
875
+ ctx.fillStyle = '#fff';
876
+ ctx.fill();
877
+ ctx.strokeStyle = color;
878
+ ctx.lineWidth = 2;
879
+ ctx.stroke();
880
+ });
881
+ }
882
+
883
+ function updateZonesList() {
884
+ if (completedZones.length === 0) {
885
+ zonesList.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No zones defined yet</div>';
886
+ return;
887
+ }
888
+
889
+ zonesList.innerHTML = completedZones.map(zone => `
890
+ <div class="zone-item">
891
+ <div>
892
+ <span class="zone-tag" style="background: ${zone.color};">${zone.type}</span>
893
+ <small style="margin-left: 8px; color: #666;">
894
+ ${zone.mode} (${zone.points.length} points)
895
+ </small>
896
+ </div>
897
+ <div class="zone-actions">
898
+ <button onclick="deleteZone('${zone.id}')" style="background: #dc3545; color: white;">Delete</button>
899
+ </div>
900
+ </div>
901
+ `).join('');
902
+ }
903
+
904
+ function generateCode() {
905
+ if (completedZones.length === 0) {
906
+ codeOutput.textContent = '# No zones defined yet\nzones = {}';
907
+ return;
908
+ }
909
+
910
+ let code = '# Generated boundary definitions\n';
911
+ code += '# Copy this code for use in your applications\n\n';
912
+
913
+ // Group zones by type
914
+ const zonesByType = {};
915
+ completedZones.forEach(zone => {
916
+ if (!zonesByType[zone.type]) {
917
+ zonesByType[zone.type] = [];
918
+ }
919
+ zonesByType[zone.type].push(zone);
920
+ });
921
+
922
+ // Generate Python dictionary
923
+ code += 'zones = {\n';
924
+ Object.keys(zonesByType).forEach(type => {
925
+ const zones = zonesByType[type];
926
+ if (zones.length === 1) {
927
+ code += ` "${type}": ${JSON.stringify(zones[0].points)},\n`;
928
+ } else {
929
+ code += ` "${type}": {\n`;
930
+ zones.forEach((zone, index) => {
931
+ code += ` "${type}_${index + 1}": ${JSON.stringify(zone.points)},\n`;
932
+ });
933
+ code += ` },\n`;
934
+ }
935
+ });
936
+ code += '}\n\n';
937
+
938
+ // Add usage examples
939
+ code += '# Usage examples:\n';
940
+ code += '# For post-processing configuration:\n';
941
+ code += '# config.customer_service.customer_areas = zones["queue"]\n';
942
+ code += '# config.advanced_tracking.boundary_config = { "points": zones["entry"] }\n\n';
943
+
944
+ // Add individual zone coordinates for easy copying
945
+ code += '# Individual zone coordinates:\n';
946
+ completedZones.forEach((zone, index) => {
947
+ code += `# ${zone.type} ${zone.mode} (${zone.points.length} points):\n`;
948
+ code += `${zone.type}_${index + 1} = ${JSON.stringify(zone.points)}\n`;
949
+ });
950
+
951
+ codeOutput.textContent = code;
952
+ }
953
+
954
+ function copyCode() {
955
+ const text = codeOutput.textContent;
956
+ navigator.clipboard.writeText(text).then(() => {
957
+ const btn = event.target;
958
+ const originalText = btn.textContent;
959
+ btn.textContent = '✅ Copied!';
960
+ btn.style.background = '#28a745';
961
+ setTimeout(() => {
962
+ btn.textContent = originalText;
963
+ btn.style.background = '';
964
+ }, 2000);
965
+ }).catch(err => {
966
+ console.error('Failed to copy: ', err);
967
+ alert('Failed to copy to clipboard. Please copy manually.');
968
+ });
969
+ }
970
+
971
+ function saveConfiguration() {
972
+ const config = {
973
+ zones: completedZones,
974
+ metadata: {
975
+ created: new Date().toISOString(),
976
+ filename: currentFile ? currentFile.name : 'unknown',
977
+ tool_version: '1.0'
978
+ }
979
+ };
980
+
981
+ const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
982
+ const url = URL.createObjectURL(blob);
983
+ const a = document.createElement('a');
984
+ a.href = url;
985
+ a.download = 'boundary_config.json';
986
+ a.click();
987
+ URL.revokeObjectURL(url);
988
+ }
989
+
990
+ function loadConfiguration() {
991
+ document.getElementById('configInput').click();
992
+ }
993
+
994
+ function handleConfigLoad(event) {
995
+ const file = event.target.files[0];
996
+ if (!file) return;
997
+
998
+ const reader = new FileReader();
999
+ reader.onload = function(e) {
1000
+ try {
1001
+ const config = JSON.parse(e.target.result);
1002
+ if (config.zones && Array.isArray(config.zones)) {
1003
+ completedZones = config.zones;
1004
+ updateZonesList();
1005
+ generateCode();
1006
+ redrawCanvas();
1007
+ alert('Configuration loaded successfully!');
1008
+ } else {
1009
+ alert('Invalid configuration file format.');
1010
+ }
1011
+ } catch (err) {
1012
+ alert('Error reading configuration file: ' + err.message);
1013
+ }
1014
+ };
1015
+ reader.readAsText(file);
1016
+ }
1017
+
1018
+ // Keyboard shortcuts
1019
+ document.addEventListener('keydown', function(e) {
1020
+ if (e.key === 'Escape') {
1021
+ cancelCurrentZone();
1022
+ } else if (e.key === 'Enter') {
1023
+ completeCurrentZone();
1024
+ } else if (e.ctrlKey && e.key === 'z') {
1025
+ e.preventDefault();
1026
+ undoLastPoint();
1027
+ }
1028
+ });
1029
+
1030
+ // Initialize
1031
+ updateModeIndicator();
1032
+ generateCode();
1033
+ setDrawingMode('polygon');
1034
+ </script>
1035
+ </body>
1036
+ </html>