isa-model 0.3.9__py3-none-any.whl → 0.4.0__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/__init__.py +1 -1
- isa_model/client.py +732 -565
- isa_model/core/cache/redis_cache.py +401 -0
- isa_model/core/config/config_manager.py +53 -10
- isa_model/core/config.py +1 -1
- isa_model/core/database/__init__.py +1 -0
- isa_model/core/database/migrations.py +277 -0
- isa_model/core/database/supabase_client.py +123 -0
- isa_model/core/models/__init__.py +37 -0
- isa_model/core/models/model_billing_tracker.py +60 -88
- isa_model/core/models/model_manager.py +36 -18
- isa_model/core/models/model_repo.py +44 -38
- 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/pricing_manager.py +2 -249
- 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 +101 -370
- isa_model/core/storage/hf_storage.py +1 -1
- isa_model/core/types.py +7 -0
- isa_model/deployment/cloud/modal/isa_audio_chatTTS_service.py +520 -0
- isa_model/deployment/cloud/modal/isa_audio_fish_service.py +0 -0
- isa_model/deployment/cloud/modal/isa_audio_openvoice_service.py +758 -0
- isa_model/deployment/cloud/modal/isa_audio_service_v2.py +1044 -0
- isa_model/deployment/cloud/modal/isa_embed_rerank_service.py +296 -0
- isa_model/deployment/cloud/modal/isa_video_hunyuan_service.py +423 -0
- isa_model/deployment/cloud/modal/isa_vision_ocr_service.py +519 -0
- isa_model/deployment/cloud/modal/isa_vision_qwen25_service.py +709 -0
- isa_model/deployment/cloud/modal/isa_vision_table_service.py +467 -323
- isa_model/deployment/cloud/modal/isa_vision_ui_service.py +607 -180
- isa_model/deployment/cloud/modal/isa_vision_ui_service_optimized.py +660 -0
- isa_model/deployment/core/deployment_manager.py +6 -4
- isa_model/deployment/services/auto_hf_modal_deployer.py +894 -0
- isa_model/eval/benchmarks/__init__.py +27 -0
- isa_model/eval/benchmarks/multimodal_datasets.py +460 -0
- isa_model/eval/benchmarks.py +244 -12
- isa_model/eval/evaluators/__init__.py +8 -2
- isa_model/eval/evaluators/audio_evaluator.py +727 -0
- isa_model/eval/evaluators/embedding_evaluator.py +742 -0
- isa_model/eval/evaluators/vision_evaluator.py +564 -0
- isa_model/eval/example_evaluation.py +395 -0
- isa_model/eval/factory.py +272 -5
- isa_model/eval/isa_benchmarks.py +700 -0
- isa_model/eval/isa_integration.py +582 -0
- isa_model/eval/metrics.py +159 -6
- isa_model/eval/tests/unit/test_basic.py +396 -0
- isa_model/inference/ai_factory.py +44 -8
- 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/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 +32 -6
- isa_model/inference/services/base_service.py +17 -1
- 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/openai_embed_service.py +2 -4
- 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/base_llm_service.py +30 -6
- isa_model/inference/services/llm/helpers/llm_adapter.py +63 -9
- isa_model/inference/services/llm/ollama_llm_service.py +2 -1
- isa_model/inference/services/llm/openai_llm_service.py +652 -55
- isa_model/inference/services/llm/yyds_llm_service.py +2 -1
- isa_model/inference/services/vision/__init__.py +5 -5
- isa_model/inference/services/vision/base_vision_service.py +118 -185
- isa_model/inference/services/vision/helpers/image_utils.py +11 -5
- isa_model/inference/services/vision/isa_vision_service.py +573 -0
- isa_model/inference/services/vision/tests/test_ocr_client.py +284 -0
- isa_model/serving/api/fastapi_server.py +88 -16
- isa_model/serving/api/middleware/auth.py +311 -0
- isa_model/serving/api/middleware/security.py +278 -0
- isa_model/serving/api/routes/analytics.py +486 -0
- isa_model/serving/api/routes/deployments.py +339 -0
- isa_model/serving/api/routes/evaluations.py +579 -0
- isa_model/serving/api/routes/logs.py +430 -0
- isa_model/serving/api/routes/settings.py +582 -0
- isa_model/serving/api/routes/unified.py +324 -165
- isa_model/serving/api/startup.py +304 -0
- isa_model/serving/modal_proxy_server.py +249 -0
- isa_model/training/__init__.py +100 -6
- isa_model/training/core/__init__.py +4 -1
- isa_model/training/examples/intelligent_training_example.py +281 -0
- isa_model/training/intelligent/__init__.py +25 -0
- isa_model/training/intelligent/decision_engine.py +643 -0
- isa_model/training/intelligent/intelligent_factory.py +888 -0
- isa_model/training/intelligent/knowledge_base.py +751 -0
- isa_model/training/intelligent/resource_optimizer.py +839 -0
- isa_model/training/intelligent/task_classifier.py +576 -0
- isa_model/training/storage/__init__.py +24 -0
- isa_model/training/storage/core_integration.py +439 -0
- isa_model/training/storage/training_repository.py +552 -0
- isa_model/training/storage/training_storage.py +628 -0
- {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/METADATA +13 -1
- isa_model-0.4.0.dist-info/RECORD +182 -0
- isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -766
- isa_model/deployment/cloud/modal/register_models.py +0 -321
- 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-0.3.9.dist-info/RECORD +0 -138
- {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/WHEEL +0 -0
- {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,519 @@
|
|
1
|
+
"""
|
2
|
+
ISA Vision OCR Service
|
3
|
+
|
4
|
+
Specialized service for multilingual OCR using SuryaOCR
|
5
|
+
Supports 90+ languages with high accuracy text detection and recognition
|
6
|
+
"""
|
7
|
+
|
8
|
+
import modal
|
9
|
+
import torch
|
10
|
+
import base64
|
11
|
+
import io
|
12
|
+
import numpy as np
|
13
|
+
from PIL import Image
|
14
|
+
from typing import Dict, List, Optional, Any
|
15
|
+
import time
|
16
|
+
import json
|
17
|
+
import os
|
18
|
+
import logging
|
19
|
+
|
20
|
+
# Define Modal application
|
21
|
+
app = modal.App("isa-vision-ocr")
|
22
|
+
|
23
|
+
# Download SuryaOCR models
|
24
|
+
def download_surya_models():
|
25
|
+
"""Download SuryaOCR models and dependencies"""
|
26
|
+
print("= Downloading SuryaOCR models...")
|
27
|
+
os.makedirs("/models", exist_ok=True)
|
28
|
+
|
29
|
+
try:
|
30
|
+
# SuryaOCR will auto-download models on first use
|
31
|
+
# Just verify the installation works
|
32
|
+
print(" SuryaOCR models will be downloaded on first use")
|
33
|
+
|
34
|
+
except Exception as e:
|
35
|
+
print(f" SuryaOCR setup warning: {e}")
|
36
|
+
|
37
|
+
print(" SuryaOCR setup completed")
|
38
|
+
|
39
|
+
# Define Modal container image
|
40
|
+
image = (
|
41
|
+
modal.Image.debian_slim(python_version="3.11")
|
42
|
+
.apt_install([
|
43
|
+
# Graphics libraries for image processing
|
44
|
+
"libgl1-mesa-glx",
|
45
|
+
"libglib2.0-0",
|
46
|
+
"libsm6",
|
47
|
+
"libxext6",
|
48
|
+
"libxrender-dev",
|
49
|
+
"libgomp1",
|
50
|
+
# Font support for multilingual text
|
51
|
+
"fontconfig",
|
52
|
+
"fonts-dejavu-core",
|
53
|
+
"fonts-liberation",
|
54
|
+
])
|
55
|
+
.pip_install([
|
56
|
+
# Core AI libraries
|
57
|
+
"torch>=2.0.0",
|
58
|
+
"torchvision",
|
59
|
+
"transformers>=4.35.0",
|
60
|
+
"huggingface_hub",
|
61
|
+
"accelerate",
|
62
|
+
|
63
|
+
# SuryaOCR specific dependencies
|
64
|
+
"surya-ocr>=0.5.0", # Latest stable version
|
65
|
+
|
66
|
+
# Image processing
|
67
|
+
"pillow>=10.0.1",
|
68
|
+
"opencv-python-headless",
|
69
|
+
"numpy>=1.24.3",
|
70
|
+
|
71
|
+
# HTTP libraries
|
72
|
+
"httpx>=0.26.0",
|
73
|
+
"requests",
|
74
|
+
|
75
|
+
# Utilities
|
76
|
+
"pydantic>=2.0.0",
|
77
|
+
"python-dotenv",
|
78
|
+
])
|
79
|
+
.run_function(download_surya_models)
|
80
|
+
.env({
|
81
|
+
"TRANSFORMERS_CACHE": "/models",
|
82
|
+
"TORCH_HOME": "/models/torch",
|
83
|
+
"SURYA_CACHE": "/models/surya",
|
84
|
+
"HF_HOME": "/models",
|
85
|
+
})
|
86
|
+
)
|
87
|
+
|
88
|
+
# SuryaOCR Service - Optimized for T4 GPU
|
89
|
+
@app.cls(
|
90
|
+
gpu="T4", # T4 4GB GPU - cost effective for OCR
|
91
|
+
image=image,
|
92
|
+
memory=8192, # 8GB RAM
|
93
|
+
timeout=1800, # 30 minutes
|
94
|
+
scaledown_window=60, # 1 minute idle timeout
|
95
|
+
min_containers=0, # Scale to zero to save costs
|
96
|
+
max_containers=20, # Support up to 20 concurrent containers
|
97
|
+
)
|
98
|
+
class SuryaOCRService:
|
99
|
+
"""
|
100
|
+
SuryaOCR Multilingual Text Detection and Recognition Service
|
101
|
+
|
102
|
+
Provides high-accuracy OCR for 90+ languages
|
103
|
+
Optimized for document processing with cost-effective deployment
|
104
|
+
"""
|
105
|
+
|
106
|
+
@modal.enter()
|
107
|
+
def load_models(self):
|
108
|
+
"""Load SuryaOCR models on container startup"""
|
109
|
+
print("= Loading SuryaOCR models...")
|
110
|
+
start_time = time.time()
|
111
|
+
|
112
|
+
# Initialize instance variables
|
113
|
+
self.models_loaded = False
|
114
|
+
self.logger = logging.getLogger(__name__)
|
115
|
+
self.request_count = 0
|
116
|
+
self.total_processing_time = 0.0
|
117
|
+
|
118
|
+
try:
|
119
|
+
# Import SuryaOCR components - correct API
|
120
|
+
from surya.recognition import RecognitionPredictor
|
121
|
+
from surya.detection import DetectionPredictor
|
122
|
+
|
123
|
+
print("= Loading SuryaOCR predictors...")
|
124
|
+
self.recognition_predictor = RecognitionPredictor()
|
125
|
+
self.detection_predictor = DetectionPredictor()
|
126
|
+
|
127
|
+
print("= SuryaOCR predictors loaded successfully")
|
128
|
+
self.models_loaded = True
|
129
|
+
|
130
|
+
load_time = time.time() - start_time
|
131
|
+
print(f" SuryaOCR models loaded successfully in {load_time:.2f}s")
|
132
|
+
|
133
|
+
except Exception as e:
|
134
|
+
print(f"L SuryaOCR model loading failed: {e}")
|
135
|
+
import traceback
|
136
|
+
traceback.print_exc()
|
137
|
+
# Don't raise - allow service to start with degraded functionality
|
138
|
+
print(" Service will run with reduced functionality")
|
139
|
+
|
140
|
+
@modal.method()
|
141
|
+
def extract_text(
|
142
|
+
self,
|
143
|
+
image_b64: str,
|
144
|
+
languages: List[str] = ["en"],
|
145
|
+
detection_threshold: float = 0.6
|
146
|
+
) -> Dict[str, Any]:
|
147
|
+
"""
|
148
|
+
Extract text from image using SuryaOCR
|
149
|
+
|
150
|
+
Args:
|
151
|
+
image_b64: Base64 encoded image
|
152
|
+
languages: List of languages to detect (e.g., ["en", "zh", "ja"])
|
153
|
+
detection_threshold: Text detection confidence threshold
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
OCR results with text, bounding boxes, and metadata
|
157
|
+
"""
|
158
|
+
start_time = time.time()
|
159
|
+
self.request_count += 1
|
160
|
+
|
161
|
+
try:
|
162
|
+
# Validate models are loaded
|
163
|
+
if not hasattr(self, 'models_loaded') or not self.models_loaded:
|
164
|
+
raise RuntimeError("SuryaOCR models not loaded")
|
165
|
+
|
166
|
+
# Decode and process image
|
167
|
+
image = self._decode_image(image_b64)
|
168
|
+
|
169
|
+
# Run SuryaOCR detection and recognition
|
170
|
+
text_results = self._run_surya_ocr(image, languages, detection_threshold)
|
171
|
+
|
172
|
+
processing_time = time.time() - start_time
|
173
|
+
self.total_processing_time += processing_time
|
174
|
+
|
175
|
+
# Calculate cost (T4 GPU: ~$0.40/hour)
|
176
|
+
gpu_cost = (processing_time / 3600) * 0.40
|
177
|
+
|
178
|
+
result = {
|
179
|
+
'success': True,
|
180
|
+
'service': 'isa-vision-ocr',
|
181
|
+
'provider': 'ISA',
|
182
|
+
'text_results': text_results,
|
183
|
+
'text_count': len(text_results),
|
184
|
+
'languages': languages,
|
185
|
+
'processing_time': processing_time,
|
186
|
+
'ocr_method': 'surya-ocr',
|
187
|
+
'billing': {
|
188
|
+
'request_id': f"req_{self.request_count}_{int(time.time())}",
|
189
|
+
'gpu_seconds': processing_time,
|
190
|
+
'estimated_cost_usd': round(gpu_cost, 6),
|
191
|
+
'gpu_type': 'T4'
|
192
|
+
},
|
193
|
+
'model_info': {
|
194
|
+
'model': 'SuryaOCR Detection + Recognition',
|
195
|
+
'provider': 'ISA',
|
196
|
+
'gpu': 'T4',
|
197
|
+
'languages_supported': 90,
|
198
|
+
'container_id': os.environ.get('MODAL_TASK_ID', 'unknown')
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
# Output JSON for client parsing
|
203
|
+
print("=== JSON_RESULT_START ===")
|
204
|
+
print(json.dumps(result, default=str))
|
205
|
+
print("=== JSON_RESULT_END ===")
|
206
|
+
|
207
|
+
return result
|
208
|
+
|
209
|
+
except Exception as e:
|
210
|
+
processing_time = time.time() - start_time
|
211
|
+
self.logger.error(f"SuryaOCR extraction failed: {e}")
|
212
|
+
error_result = {
|
213
|
+
'success': False,
|
214
|
+
'service': 'isa-vision-ocr',
|
215
|
+
'provider': 'ISA',
|
216
|
+
'error': str(e),
|
217
|
+
'processing_time': processing_time,
|
218
|
+
'billing': {
|
219
|
+
'request_id': f"req_{self.request_count}_{int(time.time())}",
|
220
|
+
'gpu_seconds': processing_time,
|
221
|
+
'estimated_cost_usd': round((processing_time / 3600) * 0.40, 6),
|
222
|
+
'gpu_type': 'T4'
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
print("=== JSON_RESULT_START ===")
|
227
|
+
print(json.dumps(error_result, default=str))
|
228
|
+
print("=== JSON_RESULT_END ===")
|
229
|
+
|
230
|
+
return error_result
|
231
|
+
|
232
|
+
def _run_surya_ocr(self, image: Image.Image, languages: List[str], threshold: float) -> List[Dict[str, Any]]:
|
233
|
+
"""Run SuryaOCR detection and recognition"""
|
234
|
+
print(f" Running SuryaOCR for languages: {languages}")
|
235
|
+
|
236
|
+
try:
|
237
|
+
# Run OCR with SuryaOCR using correct API
|
238
|
+
predictions = self.recognition_predictor(
|
239
|
+
[image],
|
240
|
+
det_predictor=self.detection_predictor
|
241
|
+
)
|
242
|
+
|
243
|
+
text_results = []
|
244
|
+
|
245
|
+
# Process OCR results
|
246
|
+
if predictions and len(predictions) > 0:
|
247
|
+
prediction = predictions[0] # First (and only) image
|
248
|
+
|
249
|
+
for idx, text_line in enumerate(prediction.text_lines):
|
250
|
+
# Extract bounding box coordinates
|
251
|
+
bbox = text_line.bbox
|
252
|
+
x1, y1, x2, y2 = bbox
|
253
|
+
|
254
|
+
# Extract text and confidence
|
255
|
+
text_content = text_line.text
|
256
|
+
confidence = text_line.confidence if hasattr(text_line, 'confidence') else 0.9
|
257
|
+
|
258
|
+
# Skip low-confidence detections
|
259
|
+
if confidence < threshold:
|
260
|
+
continue
|
261
|
+
|
262
|
+
text_results.append({
|
263
|
+
'id': f'surya_{idx}',
|
264
|
+
'text': text_content,
|
265
|
+
'confidence': float(confidence),
|
266
|
+
'bbox': [int(x1), int(y1), int(x2), int(y2)],
|
267
|
+
'center': [
|
268
|
+
int((x1 + x2) // 2),
|
269
|
+
int((y1 + y2) // 2)
|
270
|
+
],
|
271
|
+
'language': languages[0] if languages else 'auto' # Primary language
|
272
|
+
})
|
273
|
+
|
274
|
+
print(f" SuryaOCR extracted {len(text_results)} text regions")
|
275
|
+
return text_results
|
276
|
+
|
277
|
+
except Exception as e:
|
278
|
+
print(f"L SuryaOCR processing failed: {e}")
|
279
|
+
import traceback
|
280
|
+
traceback.print_exc()
|
281
|
+
return []
|
282
|
+
|
283
|
+
@modal.method()
|
284
|
+
def detect_text_regions(self, image_b64: str, threshold: float = 0.6) -> Dict[str, Any]:
|
285
|
+
"""
|
286
|
+
Detect text regions only (without recognition)
|
287
|
+
|
288
|
+
Args:
|
289
|
+
image_b64: Base64 encoded image
|
290
|
+
threshold: Detection confidence threshold
|
291
|
+
|
292
|
+
Returns:
|
293
|
+
Text detection results with bounding boxes
|
294
|
+
"""
|
295
|
+
start_time = time.time()
|
296
|
+
|
297
|
+
try:
|
298
|
+
if not self.det_model or not self.det_processor:
|
299
|
+
raise RuntimeError("SuryaOCR detection model not loaded")
|
300
|
+
|
301
|
+
image = self._decode_image(image_b64)
|
302
|
+
|
303
|
+
from surya.detection import batch_text_detection
|
304
|
+
|
305
|
+
# Run text detection only
|
306
|
+
line_predictions = batch_text_detection([image], self.det_model, self.det_processor)
|
307
|
+
|
308
|
+
text_regions = []
|
309
|
+
if line_predictions and len(line_predictions) > 0:
|
310
|
+
prediction = line_predictions[0]
|
311
|
+
|
312
|
+
for idx, text_line in enumerate(prediction.bboxes):
|
313
|
+
bbox = text_line.bbox
|
314
|
+
confidence = text_line.confidence if hasattr(text_line, 'confidence') else 0.9
|
315
|
+
|
316
|
+
if confidence >= threshold:
|
317
|
+
x1, y1, x2, y2 = bbox
|
318
|
+
text_regions.append({
|
319
|
+
'id': f'region_{idx}',
|
320
|
+
'bbox': [int(x1), int(y1), int(x2), int(y2)],
|
321
|
+
'confidence': float(confidence),
|
322
|
+
'center': [int((x1 + x2) // 2), int((y1 + y2) // 2)]
|
323
|
+
})
|
324
|
+
|
325
|
+
processing_time = time.time() - start_time
|
326
|
+
|
327
|
+
return {
|
328
|
+
'success': True,
|
329
|
+
'service': 'isa-vision-ocr',
|
330
|
+
'function': 'text_detection',
|
331
|
+
'text_regions': text_regions,
|
332
|
+
'region_count': len(text_regions),
|
333
|
+
'processing_time': processing_time
|
334
|
+
}
|
335
|
+
|
336
|
+
except Exception as e:
|
337
|
+
return {
|
338
|
+
'success': False,
|
339
|
+
'service': 'isa-vision-ocr',
|
340
|
+
'function': 'text_detection',
|
341
|
+
'error': str(e),
|
342
|
+
'processing_time': time.time() - start_time
|
343
|
+
}
|
344
|
+
|
345
|
+
@modal.method()
|
346
|
+
def health_check(self) -> Dict[str, Any]:
|
347
|
+
"""Health check endpoint"""
|
348
|
+
return {
|
349
|
+
'status': 'healthy',
|
350
|
+
'service': 'isa-vision-ocr',
|
351
|
+
'provider': 'ISA',
|
352
|
+
'models_loaded': {
|
353
|
+
'detection': self.det_model is not None,
|
354
|
+
'recognition': self.rec_model is not None
|
355
|
+
},
|
356
|
+
'model_name': 'SuryaOCR Detection + Recognition',
|
357
|
+
'languages_supported': 90,
|
358
|
+
'timestamp': time.time(),
|
359
|
+
'gpu': 'T4',
|
360
|
+
'memory_usage': '8GB',
|
361
|
+
'request_count': self.request_count
|
362
|
+
}
|
363
|
+
|
364
|
+
@modal.method()
|
365
|
+
def get_usage_stats(self) -> Dict[str, Any]:
|
366
|
+
"""Get service usage statistics for billing"""
|
367
|
+
avg_processing_time = (
|
368
|
+
self.total_processing_time / self.request_count
|
369
|
+
if self.request_count > 0 else 0
|
370
|
+
)
|
371
|
+
total_cost = (self.total_processing_time / 3600) * 0.40
|
372
|
+
|
373
|
+
return {
|
374
|
+
'service': 'isa-vision-ocr',
|
375
|
+
'provider': 'ISA',
|
376
|
+
'stats': {
|
377
|
+
'total_requests': self.request_count,
|
378
|
+
'total_gpu_seconds': round(self.total_processing_time, 3),
|
379
|
+
'avg_processing_time': round(avg_processing_time, 3),
|
380
|
+
'total_cost_usd': round(total_cost, 6),
|
381
|
+
'container_id': os.environ.get('MODAL_TASK_ID', 'unknown')
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
385
|
+
def _decode_image(self, image_b64: str) -> Image.Image:
|
386
|
+
"""Decode base64 image"""
|
387
|
+
try:
|
388
|
+
# Handle data URL format
|
389
|
+
if image_b64.startswith('data:image'):
|
390
|
+
image_b64 = image_b64.split(',')[1]
|
391
|
+
|
392
|
+
# Clean up base64 string
|
393
|
+
image_b64 = image_b64.strip().replace('\n', '').replace('\r', '').replace(' ', '')
|
394
|
+
|
395
|
+
# Decode base64
|
396
|
+
image_data = base64.b64decode(image_b64)
|
397
|
+
print(f" Decoded image size: {len(image_data)} bytes")
|
398
|
+
|
399
|
+
# Open with PIL
|
400
|
+
image = Image.open(io.BytesIO(image_data))
|
401
|
+
print(f" Image format: {image.format}, size: {image.size}, mode: {image.mode}")
|
402
|
+
|
403
|
+
return image.convert('RGB')
|
404
|
+
|
405
|
+
except Exception as e:
|
406
|
+
print(f"L Image decode error: {e}")
|
407
|
+
raise e
|
408
|
+
|
409
|
+
# Auto-registration function
|
410
|
+
@app.function()
|
411
|
+
async def register_service():
|
412
|
+
"""Auto-register this service in the model registry"""
|
413
|
+
try:
|
414
|
+
import sys
|
415
|
+
from pathlib import Path
|
416
|
+
|
417
|
+
# Add project root to path for imports
|
418
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
419
|
+
sys.path.insert(0, str(project_root))
|
420
|
+
|
421
|
+
try:
|
422
|
+
from isa_model.core.models.model_manager import ModelManager
|
423
|
+
from isa_model.core.models.model_repo import ModelType, ModelCapability
|
424
|
+
except ImportError:
|
425
|
+
print(" Could not import model manager - registration skipped")
|
426
|
+
return {"success": False, "error": "Model manager not available"}
|
427
|
+
|
428
|
+
# Use ModelManager to register this service
|
429
|
+
model_manager = ModelManager()
|
430
|
+
|
431
|
+
# Register the ISA service in the registry
|
432
|
+
success = model_manager.registry.register_model(
|
433
|
+
model_id="isa-surya-ocr-service",
|
434
|
+
model_type=ModelType.VISION,
|
435
|
+
capabilities=[
|
436
|
+
ModelCapability.OCR,
|
437
|
+
ModelCapability.TEXT_DETECTION,
|
438
|
+
ModelCapability.IMAGE_ANALYSIS
|
439
|
+
],
|
440
|
+
metadata={
|
441
|
+
"description": "ISA SuryaOCR multilingual text extraction service",
|
442
|
+
"provider": "ISA",
|
443
|
+
"service_name": "isa-vision-ocr",
|
444
|
+
"service_type": "modal",
|
445
|
+
"deployment_type": "modal_gpu",
|
446
|
+
"endpoint": "https://isa-vision-ocr.modal.run",
|
447
|
+
"underlying_model": "SuryaOCR Detection + Recognition",
|
448
|
+
"gpu_requirement": "T4",
|
449
|
+
"memory_mb": 8192,
|
450
|
+
"max_containers": 20,
|
451
|
+
"cost_per_hour_usd": 0.40,
|
452
|
+
"auto_registered": True,
|
453
|
+
"registered_by": "isa_vision_ocr_service.py",
|
454
|
+
"is_service": True,
|
455
|
+
"optimized": True,
|
456
|
+
"billing_enabled": True,
|
457
|
+
"languages_supported": 90,
|
458
|
+
"multilingual": True
|
459
|
+
}
|
460
|
+
)
|
461
|
+
|
462
|
+
if success:
|
463
|
+
print(" OCR service auto-registered successfully")
|
464
|
+
else:
|
465
|
+
print(" OCR service registration failed")
|
466
|
+
|
467
|
+
return {"success": success}
|
468
|
+
|
469
|
+
except Exception as e:
|
470
|
+
print(f"L Auto-registration error: {e}")
|
471
|
+
return {"success": False, "error": str(e)}
|
472
|
+
|
473
|
+
# Deployment script
|
474
|
+
@app.function()
|
475
|
+
def deploy_info():
|
476
|
+
"""Deployment information"""
|
477
|
+
return {
|
478
|
+
"service": "ISA Vision OCR Detection",
|
479
|
+
"model": "SuryaOCR Detection + Recognition (90+ languages)",
|
480
|
+
"gpu_requirement": "T4",
|
481
|
+
"memory_requirement": "8GB",
|
482
|
+
"deploy_command": "modal deploy isa_vision_ocr_service.py"
|
483
|
+
}
|
484
|
+
|
485
|
+
# Quick deployment function
|
486
|
+
@app.function()
|
487
|
+
def deploy_service():
|
488
|
+
"""Deploy this service instantly"""
|
489
|
+
import subprocess
|
490
|
+
import os
|
491
|
+
|
492
|
+
print("= Deploying ISA Vision OCR Service...")
|
493
|
+
try:
|
494
|
+
# Get the current file path
|
495
|
+
current_file = __file__
|
496
|
+
|
497
|
+
# Run modal deploy command
|
498
|
+
result = subprocess.run(
|
499
|
+
["modal", "deploy", current_file],
|
500
|
+
capture_output=True,
|
501
|
+
text=True,
|
502
|
+
check=True
|
503
|
+
)
|
504
|
+
|
505
|
+
print(" Deployment completed successfully!")
|
506
|
+
print(f"= Output: {result.stdout}")
|
507
|
+
return {"success": True, "output": result.stdout}
|
508
|
+
|
509
|
+
except subprocess.CalledProcessError as e:
|
510
|
+
print(f"L Deployment failed: {e}")
|
511
|
+
print(f"= Error: {e.stderr}")
|
512
|
+
return {"success": False, "error": str(e), "stderr": e.stderr}
|
513
|
+
|
514
|
+
if __name__ == "__main__":
|
515
|
+
print("= ISA Vision OCR Service - Modal Deployment")
|
516
|
+
print("Deploy with: modal deploy isa_vision_ocr_service.py")
|
517
|
+
print("Or call: modal run isa_vision_ocr_service.py::deploy_service")
|
518
|
+
print("Note: Uses SuryaOCR for 90+ language support")
|
519
|
+
print("\n= Service will auto-register in model registry upon deployment")
|