isa-model 0.3.91__py3-none-any.whl → 0.4.3__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 (228) hide show
  1. isa_model/client.py +1166 -584
  2. isa_model/core/cache/redis_cache.py +410 -0
  3. isa_model/core/config/config_manager.py +282 -12
  4. isa_model/core/config.py +91 -1
  5. isa_model/core/database/__init__.py +1 -0
  6. isa_model/core/database/direct_db_client.py +114 -0
  7. isa_model/core/database/migration_manager.py +563 -0
  8. isa_model/core/database/migrations.py +297 -0
  9. isa_model/core/database/supabase_client.py +258 -0
  10. isa_model/core/dependencies.py +316 -0
  11. isa_model/core/discovery/__init__.py +19 -0
  12. isa_model/core/discovery/consul_discovery.py +190 -0
  13. isa_model/core/logging/__init__.py +54 -0
  14. isa_model/core/logging/influx_logger.py +523 -0
  15. isa_model/core/logging/loki_logger.py +160 -0
  16. isa_model/core/models/__init__.py +46 -0
  17. isa_model/core/models/config_models.py +625 -0
  18. isa_model/core/models/deployment_billing_tracker.py +430 -0
  19. isa_model/core/models/model_billing_tracker.py +60 -88
  20. isa_model/core/models/model_manager.py +66 -25
  21. isa_model/core/models/model_metadata.py +690 -0
  22. isa_model/core/models/model_repo.py +217 -55
  23. isa_model/core/models/model_statistics_tracker.py +234 -0
  24. isa_model/core/models/model_storage.py +0 -1
  25. isa_model/core/models/model_version_manager.py +959 -0
  26. isa_model/core/models/system_models.py +857 -0
  27. isa_model/core/pricing_manager.py +2 -249
  28. isa_model/core/repositories/__init__.py +9 -0
  29. isa_model/core/repositories/config_repository.py +912 -0
  30. isa_model/core/resilience/circuit_breaker.py +366 -0
  31. isa_model/core/security/secrets.py +358 -0
  32. isa_model/core/services/__init__.py +2 -4
  33. isa_model/core/services/intelligent_model_selector.py +479 -370
  34. isa_model/core/storage/hf_storage.py +2 -2
  35. isa_model/core/types.py +8 -0
  36. isa_model/deployment/__init__.py +5 -48
  37. isa_model/deployment/core/__init__.py +2 -31
  38. isa_model/deployment/core/deployment_manager.py +1278 -368
  39. isa_model/deployment/local/__init__.py +31 -0
  40. isa_model/deployment/local/config.py +248 -0
  41. isa_model/deployment/local/gpu_gateway.py +607 -0
  42. isa_model/deployment/local/health_checker.py +428 -0
  43. isa_model/deployment/local/provider.py +586 -0
  44. isa_model/deployment/local/tensorrt_service.py +621 -0
  45. isa_model/deployment/local/transformers_service.py +644 -0
  46. isa_model/deployment/local/vllm_service.py +527 -0
  47. isa_model/deployment/modal/__init__.py +8 -0
  48. isa_model/deployment/modal/config.py +136 -0
  49. isa_model/deployment/modal/deployer.py +894 -0
  50. isa_model/deployment/modal/services/__init__.py +3 -0
  51. isa_model/deployment/modal/services/audio/__init__.py +1 -0
  52. isa_model/deployment/modal/services/audio/isa_audio_chatTTS_service.py +520 -0
  53. isa_model/deployment/modal/services/audio/isa_audio_openvoice_service.py +758 -0
  54. isa_model/deployment/modal/services/audio/isa_audio_service_v2.py +1044 -0
  55. isa_model/deployment/modal/services/embedding/__init__.py +1 -0
  56. isa_model/deployment/modal/services/embedding/isa_embed_rerank_service.py +296 -0
  57. isa_model/deployment/modal/services/llm/__init__.py +1 -0
  58. isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
  59. isa_model/deployment/modal/services/video/__init__.py +1 -0
  60. isa_model/deployment/modal/services/video/isa_video_hunyuan_service.py +423 -0
  61. isa_model/deployment/modal/services/vision/__init__.py +1 -0
  62. isa_model/deployment/modal/services/vision/isa_vision_ocr_service.py +519 -0
  63. isa_model/deployment/modal/services/vision/isa_vision_qwen25_service.py +709 -0
  64. isa_model/deployment/modal/services/vision/isa_vision_table_service.py +676 -0
  65. isa_model/deployment/modal/services/vision/isa_vision_ui_service.py +833 -0
  66. isa_model/deployment/modal/services/vision/isa_vision_ui_service_optimized.py +660 -0
  67. isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
  68. isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
  69. isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
  70. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
  71. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
  72. isa_model/deployment/storage/__init__.py +5 -0
  73. isa_model/deployment/storage/deployment_repository.py +824 -0
  74. isa_model/deployment/triton/__init__.py +10 -0
  75. isa_model/deployment/triton/config.py +196 -0
  76. isa_model/deployment/triton/configs/__init__.py +1 -0
  77. isa_model/deployment/triton/provider.py +512 -0
  78. isa_model/deployment/triton/scripts/__init__.py +1 -0
  79. isa_model/deployment/triton/templates/__init__.py +1 -0
  80. isa_model/inference/__init__.py +47 -1
  81. isa_model/inference/ai_factory.py +179 -16
  82. isa_model/inference/legacy_services/__init__.py +21 -0
  83. isa_model/inference/legacy_services/model_evaluation.py +637 -0
  84. isa_model/inference/legacy_services/model_service.py +573 -0
  85. isa_model/inference/legacy_services/model_serving.py +717 -0
  86. isa_model/inference/legacy_services/model_training.py +561 -0
  87. isa_model/inference/models/__init__.py +21 -0
  88. isa_model/inference/models/inference_config.py +551 -0
  89. isa_model/inference/models/inference_record.py +675 -0
  90. isa_model/inference/models/performance_models.py +714 -0
  91. isa_model/inference/repositories/__init__.py +9 -0
  92. isa_model/inference/repositories/inference_repository.py +828 -0
  93. isa_model/inference/services/audio/__init__.py +21 -0
  94. isa_model/inference/services/audio/base_realtime_service.py +225 -0
  95. isa_model/inference/services/audio/base_stt_service.py +184 -11
  96. isa_model/inference/services/audio/isa_tts_service.py +0 -0
  97. isa_model/inference/services/audio/openai_realtime_service.py +320 -124
  98. isa_model/inference/services/audio/openai_stt_service.py +53 -11
  99. isa_model/inference/services/base_service.py +17 -1
  100. isa_model/inference/services/custom_model_manager.py +277 -0
  101. isa_model/inference/services/embedding/__init__.py +13 -0
  102. isa_model/inference/services/embedding/base_embed_service.py +111 -8
  103. isa_model/inference/services/embedding/isa_embed_service.py +305 -0
  104. isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
  105. isa_model/inference/services/embedding/openai_embed_service.py +2 -4
  106. isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
  107. isa_model/inference/services/embedding/tests/test_embedding.py +222 -0
  108. isa_model/inference/services/img/__init__.py +2 -2
  109. isa_model/inference/services/img/base_image_gen_service.py +24 -7
  110. isa_model/inference/services/img/replicate_image_gen_service.py +84 -422
  111. isa_model/inference/services/img/services/replicate_face_swap.py +193 -0
  112. isa_model/inference/services/img/services/replicate_flux.py +226 -0
  113. isa_model/inference/services/img/services/replicate_flux_kontext.py +219 -0
  114. isa_model/inference/services/img/services/replicate_sticker_maker.py +249 -0
  115. isa_model/inference/services/img/tests/test_img_client.py +297 -0
  116. isa_model/inference/services/llm/__init__.py +10 -2
  117. isa_model/inference/services/llm/base_llm_service.py +361 -26
  118. isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
  119. isa_model/inference/services/llm/helpers/llm_adapter.py +71 -12
  120. isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
  121. isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
  122. isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
  123. isa_model/inference/services/llm/local_llm_service.py +747 -0
  124. isa_model/inference/services/llm/ollama_llm_service.py +11 -3
  125. isa_model/inference/services/llm/openai_llm_service.py +670 -56
  126. isa_model/inference/services/llm/yyds_llm_service.py +10 -3
  127. isa_model/inference/services/vision/__init__.py +27 -6
  128. isa_model/inference/services/vision/base_vision_service.py +118 -185
  129. isa_model/inference/services/vision/blip_vision_service.py +359 -0
  130. isa_model/inference/services/vision/helpers/image_utils.py +19 -10
  131. isa_model/inference/services/vision/isa_vision_service.py +634 -0
  132. isa_model/inference/services/vision/openai_vision_service.py +19 -10
  133. isa_model/inference/services/vision/tests/test_ocr_client.py +284 -0
  134. isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
  135. isa_model/serving/api/cache_manager.py +245 -0
  136. isa_model/serving/api/dependencies/__init__.py +1 -0
  137. isa_model/serving/api/dependencies/auth.py +194 -0
  138. isa_model/serving/api/dependencies/database.py +139 -0
  139. isa_model/serving/api/error_handlers.py +284 -0
  140. isa_model/serving/api/fastapi_server.py +240 -18
  141. isa_model/serving/api/middleware/auth.py +317 -0
  142. isa_model/serving/api/middleware/security.py +268 -0
  143. isa_model/serving/api/middleware/tenant_context.py +414 -0
  144. isa_model/serving/api/routes/analytics.py +489 -0
  145. isa_model/serving/api/routes/config.py +645 -0
  146. isa_model/serving/api/routes/deployment_billing.py +315 -0
  147. isa_model/serving/api/routes/deployments.py +475 -0
  148. isa_model/serving/api/routes/gpu_gateway.py +440 -0
  149. isa_model/serving/api/routes/health.py +32 -12
  150. isa_model/serving/api/routes/inference_monitoring.py +486 -0
  151. isa_model/serving/api/routes/local_deployments.py +448 -0
  152. isa_model/serving/api/routes/logs.py +430 -0
  153. isa_model/serving/api/routes/settings.py +582 -0
  154. isa_model/serving/api/routes/tenants.py +575 -0
  155. isa_model/serving/api/routes/unified.py +992 -171
  156. isa_model/serving/api/routes/webhooks.py +479 -0
  157. isa_model/serving/api/startup.py +318 -0
  158. isa_model/serving/modal_proxy_server.py +249 -0
  159. isa_model/utils/gpu_utils.py +311 -0
  160. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/METADATA +76 -22
  161. isa_model-0.4.3.dist-info/RECORD +193 -0
  162. isa_model/deployment/cloud/__init__.py +0 -9
  163. isa_model/deployment/cloud/modal/__init__.py +0 -10
  164. isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -766
  165. isa_model/deployment/cloud/modal/isa_vision_table_service.py +0 -532
  166. isa_model/deployment/cloud/modal/isa_vision_ui_service.py +0 -406
  167. isa_model/deployment/cloud/modal/register_models.py +0 -321
  168. isa_model/deployment/core/deployment_config.py +0 -356
  169. isa_model/deployment/core/isa_deployment_service.py +0 -401
  170. isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
  171. isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
  172. isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
  173. isa_model/deployment/runtime/deployed_service.py +0 -338
  174. isa_model/deployment/services/__init__.py +0 -9
  175. isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
  176. isa_model/deployment/services/model_service.py +0 -332
  177. isa_model/deployment/services/service_monitor.py +0 -356
  178. isa_model/deployment/services/service_registry.py +0 -527
  179. isa_model/eval/__init__.py +0 -92
  180. isa_model/eval/benchmarks.py +0 -469
  181. isa_model/eval/config/__init__.py +0 -10
  182. isa_model/eval/config/evaluation_config.py +0 -108
  183. isa_model/eval/evaluators/__init__.py +0 -18
  184. isa_model/eval/evaluators/base_evaluator.py +0 -503
  185. isa_model/eval/evaluators/llm_evaluator.py +0 -472
  186. isa_model/eval/factory.py +0 -531
  187. isa_model/eval/infrastructure/__init__.py +0 -24
  188. isa_model/eval/infrastructure/experiment_tracker.py +0 -466
  189. isa_model/eval/metrics.py +0 -798
  190. isa_model/inference/adapter/unified_api.py +0 -248
  191. isa_model/inference/services/helpers/stacked_config.py +0 -148
  192. isa_model/inference/services/img/flux_professional_service.py +0 -603
  193. isa_model/inference/services/img/helpers/base_stacked_service.py +0 -274
  194. isa_model/inference/services/others/table_transformer_service.py +0 -61
  195. isa_model/inference/services/vision/doc_analysis_service.py +0 -640
  196. isa_model/inference/services/vision/helpers/base_stacked_service.py +0 -274
  197. isa_model/inference/services/vision/ui_analysis_service.py +0 -823
  198. isa_model/scripts/inference_tracker.py +0 -283
  199. isa_model/scripts/mlflow_manager.py +0 -379
  200. isa_model/scripts/model_registry.py +0 -465
  201. isa_model/scripts/register_models.py +0 -370
  202. isa_model/scripts/register_models_with_embeddings.py +0 -510
  203. isa_model/scripts/start_mlflow.py +0 -95
  204. isa_model/scripts/training_tracker.py +0 -257
  205. isa_model/training/__init__.py +0 -74
  206. isa_model/training/annotation/annotation_schema.py +0 -47
  207. isa_model/training/annotation/processors/annotation_processor.py +0 -126
  208. isa_model/training/annotation/storage/dataset_manager.py +0 -131
  209. isa_model/training/annotation/storage/dataset_schema.py +0 -44
  210. isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
  211. isa_model/training/annotation/tests/test_minio copy.py +0 -113
  212. isa_model/training/annotation/tests/test_minio_upload.py +0 -43
  213. isa_model/training/annotation/views/annotation_controller.py +0 -158
  214. isa_model/training/cloud/__init__.py +0 -22
  215. isa_model/training/cloud/job_orchestrator.py +0 -402
  216. isa_model/training/cloud/runpod_trainer.py +0 -454
  217. isa_model/training/cloud/storage_manager.py +0 -482
  218. isa_model/training/core/__init__.py +0 -23
  219. isa_model/training/core/config.py +0 -181
  220. isa_model/training/core/dataset.py +0 -222
  221. isa_model/training/core/trainer.py +0 -720
  222. isa_model/training/core/utils.py +0 -213
  223. isa_model/training/factory.py +0 -424
  224. isa_model-0.3.91.dist-info/RECORD +0 -138
  225. /isa_model/{core/storage/minio_storage.py → deployment/modal/services/audio/isa_audio_fish_service.py} +0 -0
  226. /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
  227. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
  228. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Test OCR Client using ISA Model Client
6
+ Tests the SuryaOCR service for text extraction through the unified client.
7
+ """
8
+
9
+ import asyncio
10
+ import logging
11
+ from typing import Dict, Any
12
+
13
+ from isa_model.client import ISAModelClient
14
+
15
+ # Set up logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class OCRTester:
20
+ """Test client for SuryaOCR service using ISA Model Client"""
21
+
22
+ def __init__(self):
23
+ self.client = ISAModelClient()
24
+
25
+ # Test configuration for OCR service
26
+ self.test_config = {
27
+ "model": "isa-suryaocr",
28
+ "provider": "isa",
29
+ "task": "extract",
30
+ "input_image": "isa_model/inference/services/vision/tests/contract.png"
31
+ }
32
+
33
+ async def test_ocr_extraction(self) -> Dict[str, Any]:
34
+ """Test OCR text extraction from contract image using unified client"""
35
+ logger.info("Testing OCR text extraction via unified client...")
36
+
37
+ try:
38
+ config = self.test_config
39
+
40
+ result = await self.client.invoke(
41
+ input_data=config["input_image"],
42
+ task=config["task"],
43
+ service_type="vision",
44
+ model=config["model"],
45
+ provider=config["provider"],
46
+ languages=["en", "zh"]
47
+ )
48
+
49
+ if result.get("success"):
50
+ response = result["result"]
51
+ logger.info(f"OCR extraction successful")
52
+
53
+ # Get extracted text
54
+ extracted_text = response.get('text', '')
55
+ text_length = len(extracted_text)
56
+ logger.info(f"Text extracted: {text_length} characters")
57
+
58
+ # Get cost information
59
+ cost = response.get('metadata', {}).get('billing', {}).get('estimated_cost_usd', 0)
60
+ logger.info(f"Cost: ${cost:.6f}")
61
+
62
+ # Log first 200 characters of extracted text for verification
63
+ if extracted_text:
64
+ preview_text = extracted_text[:200] + "..." if text_length > 200 else extracted_text
65
+ logger.info(f"Text preview: {preview_text}")
66
+
67
+ return {
68
+ "status": "success",
69
+ "result": response,
70
+ "metadata": result.get("metadata", {}),
71
+ "text_length": text_length,
72
+ "cost": cost
73
+ }
74
+ else:
75
+ error_msg = result.get("error", "Unknown error")
76
+ logger.error(f"OCR extraction failed: {error_msg}")
77
+ return {"status": "error", "error": error_msg}
78
+
79
+ except Exception as e:
80
+ logger.error(f"OCR extraction failed with exception: {e}")
81
+ return {"status": "error", "error": str(e)}
82
+
83
+ async def test_direct_vision_service(self) -> Dict[str, Any]:
84
+ """Test OCR using direct ISA vision service call"""
85
+ logger.info("Testing direct ISA vision service OCR...")
86
+
87
+ try:
88
+ from isa_model.inference import AIFactory
89
+
90
+ # Get ISA vision service directly
91
+ vision = AIFactory().get_vision(provider="isa")
92
+
93
+ # Extract text using SuryaOCR
94
+ result = await vision.extract_text(
95
+ self.test_config["input_image"],
96
+ languages=["en", "zh"]
97
+ )
98
+
99
+ if result.get('success'):
100
+ logger.info(f"Direct SuryaOCR successful")
101
+
102
+ # Get extracted text from text_results array
103
+ text_results = result.get('text_results', [])
104
+ extracted_text = ' '.join([item.get('text', '') for item in text_results])
105
+ text_length = len(extracted_text)
106
+ logger.info(f"Text extracted: {text_length} characters from {len(text_results)} detected regions")
107
+
108
+ # Get cost information
109
+ cost = result.get('billing', {}).get('estimated_cost_usd', 0)
110
+ logger.info(f"Cost: ${cost:.6f}")
111
+
112
+ # Count Chinese and English characters
113
+ chinese_chars = sum(1 for char in extracted_text if '\u4e00' <= char <= '\u9fff')
114
+ english_chars = sum(1 for char in extracted_text if char.isalpha() and ord(char) < 256)
115
+ logger.info(f"Chinese characters: {chinese_chars}, English characters: {english_chars}")
116
+
117
+ # Log text preview (first few items)
118
+ if text_results:
119
+ sample_texts = [item.get('text', '') for item in text_results[:5]]
120
+ logger.info(f"Sample extracted text: {sample_texts}")
121
+
122
+ return {
123
+ "status": "success",
124
+ "result": result,
125
+ "text_length": text_length,
126
+ "cost": cost
127
+ }
128
+ else:
129
+ error_msg = result.get("error", "Unknown error")
130
+ logger.error(f"Direct SuryaOCR failed: {error_msg}")
131
+ return {"status": "error", "error": error_msg}
132
+
133
+ except Exception as e:
134
+ logger.error(f"Direct SuryaOCR failed with exception: {e}")
135
+ return {"status": "error", "error": str(e)}
136
+
137
+ async def test_chinese_and_english_ocr(self) -> Dict[str, Any]:
138
+ """Test OCR with Chinese and English language support"""
139
+ logger.info("Testing Chinese and English OCR support...")
140
+
141
+ try:
142
+ from isa_model.inference import AIFactory
143
+
144
+ vision = AIFactory().get_vision(provider="isa")
145
+
146
+ # Test with both Chinese and English languages
147
+ result = await vision.extract_text(
148
+ self.test_config["input_image"],
149
+ languages=["zh", "en"] # Chinese first, then English
150
+ )
151
+
152
+ if result.get('success'):
153
+ logger.info(f"Multi-language OCR successful")
154
+
155
+ extracted_text = result.get('text', '')
156
+ text_length = len(extracted_text)
157
+ logger.info(f"Text length: {text_length}")
158
+
159
+ # Check for Chinese characters
160
+ chinese_chars = sum(1 for char in extracted_text if '\u4e00' <= char <= '\u9fff')
161
+ english_chars = sum(1 for char in extracted_text if char.isalpha() and ord(char) < 256)
162
+
163
+ logger.info(f"Chinese characters detected: {chinese_chars}")
164
+ logger.info(f"English characters detected: {english_chars}")
165
+
166
+ # Get cost
167
+ cost = result.get('billing', {}).get('estimated_cost_usd', 0)
168
+ logger.info(f"Cost: ${cost:.6f}")
169
+
170
+ return {
171
+ "status": "success",
172
+ "result": result,
173
+ "text_length": text_length,
174
+ "chinese_chars": chinese_chars,
175
+ "english_chars": english_chars,
176
+ "cost": cost
177
+ }
178
+ else:
179
+ error_msg = result.get("error", "Unknown error")
180
+ logger.error(f"Multi-language OCR failed: {error_msg}")
181
+ return {"status": "error", "error": error_msg}
182
+
183
+ except Exception as e:
184
+ logger.error(f"Multi-language OCR failed with exception: {e}")
185
+ return {"status": "error", "error": str(e)}
186
+
187
+ async def test_all_ocr_methods(self) -> Dict[str, Dict[str, Any]]:
188
+ """Test OCR functionality"""
189
+ logger.info("Starting SuryaOCR test using ISA Model Client...")
190
+
191
+ results = {}
192
+
193
+ # Test only the direct vision service (most comprehensive)
194
+ tests = [
195
+ ("suryaocr_extraction", self.test_direct_vision_service)
196
+ ]
197
+
198
+ for test_name, test_func in tests:
199
+ logger.info(f"\n{'='*50}")
200
+ logger.info(f"Running test: {test_name}")
201
+ logger.info(f"{'='*50}")
202
+
203
+ try:
204
+ result = await test_func()
205
+ results[test_name] = result
206
+
207
+ if result.get("status") == "success":
208
+ logger.info(f" {test_name} PASSED")
209
+ else:
210
+ logger.error(f"L {test_name} FAILED: {result.get('error', 'Unknown error')}")
211
+
212
+ except Exception as e:
213
+ logger.error(f"L {test_name} FAILED with exception: {e}")
214
+ results[test_name] = {"status": "error", "error": str(e)}
215
+
216
+ # Summary
217
+ logger.info(f"\n{'='*50}")
218
+ logger.info("TEST SUMMARY")
219
+ logger.info(f"{'='*50}")
220
+
221
+ passed = sum(1 for r in results.values() if r.get("status") == "success")
222
+ total = len(results)
223
+
224
+ logger.info(f"Passed: {passed}/{total}")
225
+
226
+ for test_name, result in results.items():
227
+ status = " PASS" if result.get("status") == "success" else "L FAIL"
228
+ logger.info(f"{test_name}: {status}")
229
+
230
+ return results
231
+
232
+ async def get_service_health(self) -> Dict[str, Any]:
233
+ """Get health status of the client and services"""
234
+ logger.info("Checking service health...")
235
+
236
+ try:
237
+ health = await self.client.health_check()
238
+ return health
239
+ except Exception as e:
240
+ logger.error(f"Health check failed: {e}")
241
+ return {"status": "error", "error": str(e)}
242
+
243
+ async def main():
244
+ """Main test function"""
245
+ tester = OCRTester()
246
+
247
+ # Get service health
248
+ logger.info("Checking service health...")
249
+ health = await tester.get_service_health()
250
+ logger.info(f"Service health: {health}")
251
+
252
+ # Run all tests
253
+ results = await tester.test_all_ocr_methods()
254
+
255
+ # Calculate total cost
256
+ total_cost = 0.0
257
+ for test_name, result in results.items():
258
+ if result.get("status") == "success":
259
+ cost = result.get("cost", 0.0)
260
+ total_cost += cost
261
+
262
+ logger.info(f"\nTotal cost for all OCR tests: ${total_cost:.6f}")
263
+
264
+ # Summary of text extraction results
265
+ logger.info(f"\n{'='*50}")
266
+ logger.info("TEXT EXTRACTION SUMMARY")
267
+ logger.info(f"{'='*50}")
268
+
269
+ for test_name, result in results.items():
270
+ if result.get("status") == "success":
271
+ text_length = result.get("text_length", 0)
272
+ cost = result.get("cost", 0)
273
+ logger.info(f"{test_name}: {text_length} chars extracted, ${cost:.6f}")
274
+
275
+ # Show language breakdown if available
276
+ if "chinese_chars" in result and "english_chars" in result:
277
+ logger.info(f" - Chinese: {result['chinese_chars']} chars")
278
+ logger.info(f" - English: {result['english_chars']} chars")
279
+
280
+ return results
281
+
282
+ if __name__ == "__main__":
283
+ # Run the tests
284
+ results = asyncio.run(main())
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ VGG16 Vision Service
4
+ Computer vision service using VGG16 for image classification
5
+ Based on the aircraft damage detection notebook implementation
6
+ """
7
+
8
+ import os
9
+ import numpy as np
10
+ from typing import Dict, List, Any, Optional, Union, BinaryIO
11
+ import logging
12
+ from PIL import Image
13
+ import io
14
+
15
+ from .base_vision_service import BaseVisionService
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def _lazy_import_vgg16_deps():
20
+ """Lazy import VGG16 dependencies"""
21
+ try:
22
+ import tensorflow as tf
23
+ from tensorflow.keras.applications import VGG16
24
+ from tensorflow.keras.layers import Dense, Dropout, Flatten
25
+ from tensorflow.keras.models import Sequential, Model
26
+ from tensorflow.keras.optimizers import Adam
27
+ from tensorflow.keras.preprocessing.image import ImageDataGenerator
28
+
29
+ return {
30
+ 'tf': tf,
31
+ 'VGG16': VGG16,
32
+ 'Dense': Dense,
33
+ 'Dropout': Dropout,
34
+ 'Flatten': Flatten,
35
+ 'Sequential': Sequential,
36
+ 'Model': Model,
37
+ 'Adam': Adam,
38
+ 'ImageDataGenerator': ImageDataGenerator,
39
+ 'available': True
40
+ }
41
+ except ImportError as e:
42
+ logger.warning(f"VGG16 dependencies not available: {e}")
43
+ return {'available': False}
44
+
45
+ class VGG16VisionService(BaseVisionService):
46
+ """
47
+ VGG16-based vision service for image classification
48
+ Provides an alternative implementation to VLM-based classification
49
+ """
50
+
51
+ def __init__(self, model_path: Optional[str] = None, class_names: Optional[List[str]] = None):
52
+ """
53
+ Initialize VGG16 vision service
54
+
55
+ Args:
56
+ model_path: Path to trained VGG16 model
57
+ class_names: List of class names for classification
58
+ """
59
+ super().__init__()
60
+
61
+ self.model_path = model_path
62
+ self.class_names = class_names or ["class_0", "class_1"]
63
+ self.model = None
64
+ self.input_shape = (224, 224, 3)
65
+
66
+ # Lazy load dependencies
67
+ self.vgg16_components = _lazy_import_vgg16_deps()
68
+
69
+ if not self.vgg16_components['available']:
70
+ raise ImportError("TensorFlow and VGG16 dependencies are required")
71
+
72
+ # Load model if path provided
73
+ if model_path and os.path.exists(model_path):
74
+ self._load_model(model_path)
75
+
76
+ def _load_model(self, model_path: str):
77
+ """Load trained VGG16 model"""
78
+ try:
79
+ tf = self.vgg16_components['tf']
80
+ self.model = tf.keras.models.load_model(model_path)
81
+ logger.info(f"VGG16 model loaded from {model_path}")
82
+ except Exception as e:
83
+ logger.error(f"Error loading VGG16 model: {e}")
84
+ raise
85
+
86
+ def _preprocess_image(self, image: Union[str, BinaryIO]) -> np.ndarray:
87
+ """
88
+ Preprocess image for VGG16 input
89
+
90
+ Args:
91
+ image: Image path or binary data
92
+
93
+ Returns:
94
+ Preprocessed image array
95
+ """
96
+ try:
97
+ # Handle different image input types
98
+ if isinstance(image, str):
99
+ # File path
100
+ pil_image = Image.open(image).convert('RGB')
101
+ elif hasattr(image, 'read'):
102
+ # Binary IO
103
+ image_data = image.read()
104
+ pil_image = Image.open(io.BytesIO(image_data)).convert('RGB')
105
+ else:
106
+ raise ValueError("Unsupported image format")
107
+
108
+ # Resize to VGG16 input size
109
+ pil_image = pil_image.resize((self.input_shape[0], self.input_shape[1]))
110
+
111
+ # Convert to array and normalize
112
+ image_array = np.array(pil_image) / 255.0
113
+
114
+ # Add batch dimension
115
+ image_batch = np.expand_dims(image_array, axis=0)
116
+
117
+ return image_batch, image_array
118
+
119
+ except Exception as e:
120
+ logger.error(f"Error preprocessing image: {e}")
121
+ raise
122
+
123
+ async def classify_image(self,
124
+ image: Union[str, BinaryIO],
125
+ categories: Optional[List[str]] = None) -> Dict[str, Any]:
126
+ """
127
+ Classify image using trained VGG16 model
128
+
129
+ Args:
130
+ image: Image path or binary data
131
+ categories: Optional list of categories (uses model's classes if None)
132
+
133
+ Returns:
134
+ Classification results
135
+ """
136
+ try:
137
+ if self.model is None:
138
+ return {
139
+ "error": "No trained model available. Please load a model first.",
140
+ "service": "VGG16VisionService"
141
+ }
142
+
143
+ # Preprocess image
144
+ image_batch, image_array = self._preprocess_image(image)
145
+
146
+ # Make prediction
147
+ predictions = self.model.predict(image_batch, verbose=0)
148
+
149
+ # Use provided categories or default class names
150
+ class_names = categories or self.class_names
151
+
152
+ # Process predictions based on model output
153
+ if len(predictions[0]) == 1: # Binary classification
154
+ predicted_class_idx = int(predictions[0] > 0.5)
155
+ confidence = float(predictions[0][0]) if predicted_class_idx == 1 else float(1 - predictions[0][0])
156
+
157
+ # Create probability distribution
158
+ probabilities = {
159
+ class_names[0]: float(1 - predictions[0][0]),
160
+ class_names[1]: float(predictions[0][0])
161
+ }
162
+ else: # Multiclass classification
163
+ predicted_class_idx = np.argmax(predictions[0])
164
+ confidence = float(predictions[0][predicted_class_idx])
165
+
166
+ # Create probability distribution
167
+ probabilities = {
168
+ class_names[i]: float(predictions[0][i])
169
+ for i in range(min(len(class_names), len(predictions[0])))
170
+ }
171
+
172
+ predicted_class = class_names[predicted_class_idx] if predicted_class_idx < len(class_names) else f"class_{predicted_class_idx}"
173
+
174
+ return {
175
+ "task": "classify",
176
+ "service": "VGG16VisionService",
177
+ "predicted_class": predicted_class,
178
+ "confidence": confidence,
179
+ "probabilities": probabilities,
180
+ "model_type": "VGG16",
181
+ "success": True
182
+ }
183
+
184
+ except Exception as e:
185
+ logger.error(f"Error classifying image: {e}")
186
+ return {
187
+ "error": str(e),
188
+ "service": "VGG16VisionService",
189
+ "success": False
190
+ }
191
+
192
+ async def analyze_image(self,
193
+ image: Union[str, BinaryIO],
194
+ prompt: Optional[str] = None,
195
+ max_tokens: int = 1000) -> Dict[str, Any]:
196
+ """
197
+ Analyze image using VGG16 classification
198
+
199
+ Args:
200
+ image: Image path or binary data
201
+ prompt: Optional prompt (used to guide interpretation)
202
+ max_tokens: Not used for classification
203
+
204
+ Returns:
205
+ Analysis results
206
+ """
207
+ # For VGG16, analysis is essentially classification
208
+ classification_result = await self.classify_image(image)
209
+
210
+ if classification_result.get("success"):
211
+ # Create analysis text based on classification
212
+ predicted_class = classification_result["predicted_class"]
213
+ confidence = classification_result["confidence"]
214
+
215
+ analysis_text = f"The image has been classified as '{predicted_class}' with {confidence:.2%} confidence."
216
+
217
+ if prompt:
218
+ analysis_text += f" Analysis context: {prompt}"
219
+
220
+ return {
221
+ "task": "analyze",
222
+ "service": "VGG16VisionService",
223
+ "text": analysis_text,
224
+ "confidence": confidence,
225
+ "classification": classification_result,
226
+ "success": True
227
+ }
228
+ else:
229
+ return classification_result
230
+
231
+ def set_class_names(self, class_names: List[str]):
232
+ """Set class names for classification"""
233
+ self.class_names = class_names
234
+
235
+ def load_trained_model(self, model_path: str, class_names: Optional[List[str]] = None):
236
+ """
237
+ Load a trained VGG16 model
238
+
239
+ Args:
240
+ model_path: Path to the trained model
241
+ class_names: Optional class names
242
+ """
243
+ self._load_model(model_path)
244
+ if class_names:
245
+ self.set_class_names(class_names)
246
+
247
+ def get_service_info(self) -> Dict[str, Any]:
248
+ """Get service information"""
249
+ return {
250
+ "service_name": "VGG16VisionService",
251
+ "model_type": "VGG16",
252
+ "capabilities": ["classify", "analyze"],
253
+ "model_loaded": self.model is not None,
254
+ "input_shape": self.input_shape,
255
+ "class_names": self.class_names,
256
+ "dependencies_available": self.vgg16_components['available']
257
+ }