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.
- isa_model/client.py +1166 -584
- isa_model/core/cache/redis_cache.py +410 -0
- isa_model/core/config/config_manager.py +282 -12
- isa_model/core/config.py +91 -1
- isa_model/core/database/__init__.py +1 -0
- isa_model/core/database/direct_db_client.py +114 -0
- isa_model/core/database/migration_manager.py +563 -0
- isa_model/core/database/migrations.py +297 -0
- isa_model/core/database/supabase_client.py +258 -0
- isa_model/core/dependencies.py +316 -0
- isa_model/core/discovery/__init__.py +19 -0
- isa_model/core/discovery/consul_discovery.py +190 -0
- isa_model/core/logging/__init__.py +54 -0
- isa_model/core/logging/influx_logger.py +523 -0
- isa_model/core/logging/loki_logger.py +160 -0
- isa_model/core/models/__init__.py +46 -0
- isa_model/core/models/config_models.py +625 -0
- isa_model/core/models/deployment_billing_tracker.py +430 -0
- isa_model/core/models/model_billing_tracker.py +60 -88
- isa_model/core/models/model_manager.py +66 -25
- isa_model/core/models/model_metadata.py +690 -0
- isa_model/core/models/model_repo.py +217 -55
- isa_model/core/models/model_statistics_tracker.py +234 -0
- isa_model/core/models/model_storage.py +0 -1
- isa_model/core/models/model_version_manager.py +959 -0
- isa_model/core/models/system_models.py +857 -0
- isa_model/core/pricing_manager.py +2 -249
- isa_model/core/repositories/__init__.py +9 -0
- isa_model/core/repositories/config_repository.py +912 -0
- isa_model/core/resilience/circuit_breaker.py +366 -0
- isa_model/core/security/secrets.py +358 -0
- isa_model/core/services/__init__.py +2 -4
- isa_model/core/services/intelligent_model_selector.py +479 -370
- isa_model/core/storage/hf_storage.py +2 -2
- isa_model/core/types.py +8 -0
- isa_model/deployment/__init__.py +5 -48
- isa_model/deployment/core/__init__.py +2 -31
- isa_model/deployment/core/deployment_manager.py +1278 -368
- isa_model/deployment/local/__init__.py +31 -0
- isa_model/deployment/local/config.py +248 -0
- isa_model/deployment/local/gpu_gateway.py +607 -0
- isa_model/deployment/local/health_checker.py +428 -0
- isa_model/deployment/local/provider.py +586 -0
- isa_model/deployment/local/tensorrt_service.py +621 -0
- isa_model/deployment/local/transformers_service.py +644 -0
- isa_model/deployment/local/vllm_service.py +527 -0
- isa_model/deployment/modal/__init__.py +8 -0
- isa_model/deployment/modal/config.py +136 -0
- isa_model/deployment/modal/deployer.py +894 -0
- isa_model/deployment/modal/services/__init__.py +3 -0
- isa_model/deployment/modal/services/audio/__init__.py +1 -0
- isa_model/deployment/modal/services/audio/isa_audio_chatTTS_service.py +520 -0
- isa_model/deployment/modal/services/audio/isa_audio_openvoice_service.py +758 -0
- isa_model/deployment/modal/services/audio/isa_audio_service_v2.py +1044 -0
- isa_model/deployment/modal/services/embedding/__init__.py +1 -0
- isa_model/deployment/modal/services/embedding/isa_embed_rerank_service.py +296 -0
- isa_model/deployment/modal/services/llm/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
- isa_model/deployment/modal/services/video/__init__.py +1 -0
- isa_model/deployment/modal/services/video/isa_video_hunyuan_service.py +423 -0
- isa_model/deployment/modal/services/vision/__init__.py +1 -0
- isa_model/deployment/modal/services/vision/isa_vision_ocr_service.py +519 -0
- isa_model/deployment/modal/services/vision/isa_vision_qwen25_service.py +709 -0
- isa_model/deployment/modal/services/vision/isa_vision_table_service.py +676 -0
- isa_model/deployment/modal/services/vision/isa_vision_ui_service.py +833 -0
- isa_model/deployment/modal/services/vision/isa_vision_ui_service_optimized.py +660 -0
- isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
- isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
- isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/storage/__init__.py +5 -0
- isa_model/deployment/storage/deployment_repository.py +824 -0
- isa_model/deployment/triton/__init__.py +10 -0
- isa_model/deployment/triton/config.py +196 -0
- isa_model/deployment/triton/configs/__init__.py +1 -0
- isa_model/deployment/triton/provider.py +512 -0
- isa_model/deployment/triton/scripts/__init__.py +1 -0
- isa_model/deployment/triton/templates/__init__.py +1 -0
- isa_model/inference/__init__.py +47 -1
- isa_model/inference/ai_factory.py +179 -16
- isa_model/inference/legacy_services/__init__.py +21 -0
- isa_model/inference/legacy_services/model_evaluation.py +637 -0
- isa_model/inference/legacy_services/model_service.py +573 -0
- isa_model/inference/legacy_services/model_serving.py +717 -0
- isa_model/inference/legacy_services/model_training.py +561 -0
- isa_model/inference/models/__init__.py +21 -0
- isa_model/inference/models/inference_config.py +551 -0
- isa_model/inference/models/inference_record.py +675 -0
- isa_model/inference/models/performance_models.py +714 -0
- isa_model/inference/repositories/__init__.py +9 -0
- isa_model/inference/repositories/inference_repository.py +828 -0
- isa_model/inference/services/audio/__init__.py +21 -0
- isa_model/inference/services/audio/base_realtime_service.py +225 -0
- isa_model/inference/services/audio/base_stt_service.py +184 -11
- isa_model/inference/services/audio/isa_tts_service.py +0 -0
- isa_model/inference/services/audio/openai_realtime_service.py +320 -124
- isa_model/inference/services/audio/openai_stt_service.py +53 -11
- isa_model/inference/services/base_service.py +17 -1
- isa_model/inference/services/custom_model_manager.py +277 -0
- isa_model/inference/services/embedding/__init__.py +13 -0
- isa_model/inference/services/embedding/base_embed_service.py +111 -8
- isa_model/inference/services/embedding/isa_embed_service.py +305 -0
- isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
- isa_model/inference/services/embedding/openai_embed_service.py +2 -4
- isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
- isa_model/inference/services/embedding/tests/test_embedding.py +222 -0
- isa_model/inference/services/img/__init__.py +2 -2
- isa_model/inference/services/img/base_image_gen_service.py +24 -7
- isa_model/inference/services/img/replicate_image_gen_service.py +84 -422
- isa_model/inference/services/img/services/replicate_face_swap.py +193 -0
- isa_model/inference/services/img/services/replicate_flux.py +226 -0
- isa_model/inference/services/img/services/replicate_flux_kontext.py +219 -0
- isa_model/inference/services/img/services/replicate_sticker_maker.py +249 -0
- isa_model/inference/services/img/tests/test_img_client.py +297 -0
- isa_model/inference/services/llm/__init__.py +10 -2
- isa_model/inference/services/llm/base_llm_service.py +361 -26
- isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
- isa_model/inference/services/llm/helpers/llm_adapter.py +71 -12
- isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
- isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
- isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
- isa_model/inference/services/llm/local_llm_service.py +747 -0
- isa_model/inference/services/llm/ollama_llm_service.py +11 -3
- isa_model/inference/services/llm/openai_llm_service.py +670 -56
- isa_model/inference/services/llm/yyds_llm_service.py +10 -3
- isa_model/inference/services/vision/__init__.py +27 -6
- isa_model/inference/services/vision/base_vision_service.py +118 -185
- isa_model/inference/services/vision/blip_vision_service.py +359 -0
- isa_model/inference/services/vision/helpers/image_utils.py +19 -10
- isa_model/inference/services/vision/isa_vision_service.py +634 -0
- isa_model/inference/services/vision/openai_vision_service.py +19 -10
- isa_model/inference/services/vision/tests/test_ocr_client.py +284 -0
- isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
- isa_model/serving/api/cache_manager.py +245 -0
- isa_model/serving/api/dependencies/__init__.py +1 -0
- isa_model/serving/api/dependencies/auth.py +194 -0
- isa_model/serving/api/dependencies/database.py +139 -0
- isa_model/serving/api/error_handlers.py +284 -0
- isa_model/serving/api/fastapi_server.py +240 -18
- isa_model/serving/api/middleware/auth.py +317 -0
- isa_model/serving/api/middleware/security.py +268 -0
- isa_model/serving/api/middleware/tenant_context.py +414 -0
- isa_model/serving/api/routes/analytics.py +489 -0
- isa_model/serving/api/routes/config.py +645 -0
- isa_model/serving/api/routes/deployment_billing.py +315 -0
- isa_model/serving/api/routes/deployments.py +475 -0
- isa_model/serving/api/routes/gpu_gateway.py +440 -0
- isa_model/serving/api/routes/health.py +32 -12
- isa_model/serving/api/routes/inference_monitoring.py +486 -0
- isa_model/serving/api/routes/local_deployments.py +448 -0
- isa_model/serving/api/routes/logs.py +430 -0
- isa_model/serving/api/routes/settings.py +582 -0
- isa_model/serving/api/routes/tenants.py +575 -0
- isa_model/serving/api/routes/unified.py +992 -171
- isa_model/serving/api/routes/webhooks.py +479 -0
- isa_model/serving/api/startup.py +318 -0
- isa_model/serving/modal_proxy_server.py +249 -0
- isa_model/utils/gpu_utils.py +311 -0
- {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/METADATA +76 -22
- isa_model-0.4.3.dist-info/RECORD +193 -0
- isa_model/deployment/cloud/__init__.py +0 -9
- isa_model/deployment/cloud/modal/__init__.py +0 -10
- isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -766
- isa_model/deployment/cloud/modal/isa_vision_table_service.py +0 -532
- isa_model/deployment/cloud/modal/isa_vision_ui_service.py +0 -406
- isa_model/deployment/cloud/modal/register_models.py +0 -321
- isa_model/deployment/core/deployment_config.py +0 -356
- isa_model/deployment/core/isa_deployment_service.py +0 -401
- isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
- isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
- isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
- isa_model/deployment/runtime/deployed_service.py +0 -338
- isa_model/deployment/services/__init__.py +0 -9
- isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
- isa_model/deployment/services/model_service.py +0 -332
- isa_model/deployment/services/service_monitor.py +0 -356
- isa_model/deployment/services/service_registry.py +0 -527
- isa_model/eval/__init__.py +0 -92
- isa_model/eval/benchmarks.py +0 -469
- isa_model/eval/config/__init__.py +0 -10
- isa_model/eval/config/evaluation_config.py +0 -108
- isa_model/eval/evaluators/__init__.py +0 -18
- isa_model/eval/evaluators/base_evaluator.py +0 -503
- isa_model/eval/evaluators/llm_evaluator.py +0 -472
- isa_model/eval/factory.py +0 -531
- isa_model/eval/infrastructure/__init__.py +0 -24
- isa_model/eval/infrastructure/experiment_tracker.py +0 -466
- isa_model/eval/metrics.py +0 -798
- isa_model/inference/adapter/unified_api.py +0 -248
- isa_model/inference/services/helpers/stacked_config.py +0 -148
- isa_model/inference/services/img/flux_professional_service.py +0 -603
- isa_model/inference/services/img/helpers/base_stacked_service.py +0 -274
- isa_model/inference/services/others/table_transformer_service.py +0 -61
- isa_model/inference/services/vision/doc_analysis_service.py +0 -640
- isa_model/inference/services/vision/helpers/base_stacked_service.py +0 -274
- isa_model/inference/services/vision/ui_analysis_service.py +0 -823
- isa_model/scripts/inference_tracker.py +0 -283
- isa_model/scripts/mlflow_manager.py +0 -379
- isa_model/scripts/model_registry.py +0 -465
- isa_model/scripts/register_models.py +0 -370
- isa_model/scripts/register_models_with_embeddings.py +0 -510
- isa_model/scripts/start_mlflow.py +0 -95
- isa_model/scripts/training_tracker.py +0 -257
- isa_model/training/__init__.py +0 -74
- isa_model/training/annotation/annotation_schema.py +0 -47
- isa_model/training/annotation/processors/annotation_processor.py +0 -126
- isa_model/training/annotation/storage/dataset_manager.py +0 -131
- isa_model/training/annotation/storage/dataset_schema.py +0 -44
- isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
- isa_model/training/annotation/tests/test_minio copy.py +0 -113
- isa_model/training/annotation/tests/test_minio_upload.py +0 -43
- isa_model/training/annotation/views/annotation_controller.py +0 -158
- isa_model/training/cloud/__init__.py +0 -22
- isa_model/training/cloud/job_orchestrator.py +0 -402
- isa_model/training/cloud/runpod_trainer.py +0 -454
- isa_model/training/cloud/storage_manager.py +0 -482
- isa_model/training/core/__init__.py +0 -23
- isa_model/training/core/config.py +0 -181
- isa_model/training/core/dataset.py +0 -222
- isa_model/training/core/trainer.py +0 -720
- isa_model/training/core/utils.py +0 -213
- isa_model/training/factory.py +0 -424
- isa_model-0.3.91.dist-info/RECORD +0 -138
- /isa_model/{core/storage/minio_storage.py → deployment/modal/services/audio/isa_audio_fish_service.py} +0 -0
- /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
- {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
- {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
|
+
}
|