File size: 36,848 Bytes
2f4af3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36331c6
 
 
 
2f4af3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36331c6
 
2f4af3f
 
36331c6
 
2f4af3f
 
 
36331c6
 
2f4af3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
import os
import cv2
import numpy as np
import re
import time
from PIL import Image, ImageEnhance, ImageFilter
import torch
from transformers import CLIPProcessor, CLIPModel, AutoTokenizer, AutoModelForSeq2SeqLM
from .base_processor import BaseScriptProcessor
from utils.text_utils import is_gibberish

BACKEND_MODELS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "models"))
CUNEIFORM_MODEL_DIR = os.path.join(BACKEND_MODELS_DIR, "cuneiform")


class CuneiformProcessor(BaseScriptProcessor):
    def __init__(self, groq_client, references, clip_classifier):
        super().__init__(groq_client, references, clip_classifier)
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        
        self.clip_model = None
        self.clip_processor = None
        self.clip_available = False
        
        self.cuneiform_model = None
        self.cuneiform_tokenizer = None
        self.translator_available = False
        
        # Register for dynamic VRAM management
        from utils.gpu_diagnostics import register_processor
        register_processor("cuneiform", self)
        
    @property
    def cuneiform_available(self):
        """Property to match interface expected by ScriptDetectionService"""
        # Always return True since we load lazily on demand
        return True
    

    def setup_cuneiform_clip(self):
        """Setup CLIP for cuneiform visual recognition - MUCH better than OCR"""
        try:
            from utils.gpu_diagnostics import reclaim_vram_for
            reclaim_vram_for("cuneiform")
            
            print("[INFO] Lazily loading CLIP for cuneiform visual recognition...")
            
            # Use a powerful CLIP model for better ancient script understanding
            model_name = "openai/clip-vit-large-patch14"
            
            import os
            HF_TOKEN = os.getenv("HF_TOKEN")
            self.clip_processor = CLIPProcessor.from_pretrained(model_name, token=HF_TOKEN)
            self.clip_model = CLIPModel.from_pretrained(model_name, token=HF_TOKEN)
            
            self.clip_model.to(self.device)
            self.clip_model.eval()  # Put in evaluation mode
            
            from utils.gpu_diagnostics import log_model_device
            log_model_device("Cuneiform CLIP Recognition", self.device)
            
            # Define cuneiform sign categories for CLIP classification
            self.cuneiform_signs = [
                "ancient Sumerian cuneiform sign AN meaning god or heaven",
                "ancient Akkadian cuneiform sign LUGAL meaning king or ruler",
                "ancient cuneiform sign KI meaning earth or place",
                "ancient cuneiform sign DINGIR divine determinative marker",
                "ancient cuneiform sign UD meaning day or sun",
                "ancient cuneiform sign E meaning house or temple",
                "ancient cuneiform sign EN meaning lord or priest",
                "ancient cuneiform sign NIN meaning lady or queen",
                "ancient cuneiform administrative record with numbers",
                "ancient cuneiform legal contract or treaty text",
                "ancient cuneiform royal inscription or decree",
                "ancient cuneiform literary or mythological text",
                "ancient cuneiform school exercise or practice tablet"
            ]
            
            # Tablet layout descriptions for structural analysis
            self.tablet_layouts = [
                "clay tablet with cuneiform text arranged in horizontal lines",
                "cuneiform tablet with vertical column organization",
                "administrative record tablet with numerical entries",
                "legal document tablet with witness signatures",
                "literary tablet with continuous narrative text",
                "damaged or fragmentary cuneiform tablet",
                "clear well-preserved cuneiform inscription",
                "practice tablet with student exercises"
            ]
            
            print(f"[INFO] CLIP cuneiform recognition loaded on {self.device}")
            print("[INFO] Using visual pattern recognition instead of character OCR")
            self.clip_available = True
            
        except Exception as e:
            print(f"[ERROR] CLIP cuneiform setup failed: {e}")
            self.clip_available = False

    def setup_praeclarum_translator(self):
        """Setup praeclarum translation model for converting recognized content"""
        try:
            from utils.gpu_diagnostics import reclaim_vram_for
            reclaim_vram_for("cuneiform")
            
            print("[INFO] Lazily loading praeclarum cuneiform translation model...")
            
            import os
            HF_TOKEN = os.getenv("HF_TOKEN")
            self.cuneiform_tokenizer = AutoTokenizer.from_pretrained(
                "praeclarum/cuneiform",
                cache_dir=CUNEIFORM_MODEL_DIR,
                token=HF_TOKEN
            )
            self.cuneiform_model = AutoModelForSeq2SeqLM.from_pretrained(
                "praeclarum/cuneiform",
                cache_dir=CUNEIFORM_MODEL_DIR,
                token=HF_TOKEN
            )
            self.cuneiform_model.to(self.device)
            self.cuneiform_model.eval()  # Put in evaluation mode
            
            from utils.gpu_diagnostics import log_model_device
            log_model_device("Cuneiform Translator (Praeclarum T5)", self.device)
            
            self.translator_available = True
            print("[INFO] Cuneiform translator ready for CLIP-recognized content")
            
        except Exception as e:
            print(f"[ERROR] Translation model setup failed: {e}")
            self.translator_available = False

    def detect_script(self, image_path):
        """Detection handled by enhanced CLIP classification"""
        try:
            if not self.clip_available:
                print("[ERROR] No cuneiform processing engines available")
                return False, 0.0
            
            print(f"[INFO] Cuneiform processor activated - Using CLIP visual recognition")
            return True, 0.95
            
        except Exception as e:
            print(f"[ERROR] Cuneiform detection failed: {e}")
            return False, 0.0

    def extract_text(self, image_path):
        """Extract cuneiform using CLIP visual recognition instead of OCR"""
        if self.clip_model is None:
            self.setup_cuneiform_clip()
        else:
            from utils.gpu_diagnostics import reclaim_vram_for
            reclaim_vram_for("cuneiform")
            if str(next(self.clip_model.parameters()).device) != str(self.device):
                print(f"[VRAM MANAGER] Activating Cuneiform CLIP model on {self.device}...")
                self.clip_model.to(self.device)
                
        if not getattr(self, 'clip_available', False) or self.clip_model is None:
            return "CUNEIFORM_CLIP_FAILED: Visual recognition model not available"
            
        try:
            start_time = time.time()
            
            # Method 1: CLIP-based visual analysis
            print("[INFO] Analyzing cuneiform using CLIP visual recognition...")
            visual_analysis = self._analyze_cuneiform_with_clip(image_path)
            
            if visual_analysis and visual_analysis['confidence'] > 0.3:
                processing_time = time.time() - start_time
                print(f"[SUCCESS] CLIP visual analysis completed in {processing_time:.2f}s")
                return visual_analysis['description']
            
            # Method 2: Fallback to basic tablet description
            tablet_description = self._describe_tablet_layout(image_path)
            if tablet_description:
                return tablet_description
            
            return "CUNEIFORM_VISUAL_ANALYSIS_INCOMPLETE: Clay tablet detected but content analysis requires higher resolution or clearer image"
            
        except Exception as e:
            print(f"[ERROR] CLIP cuneiform analysis failed: {e}")
            return f"CUNEIFORM_ERROR: {str(e)}"

    def _analyze_cuneiform_with_clip(self, image_path):
        """Use CLIP to analyze cuneiform content visually"""
        try:
            image = Image.open(image_path).convert("RGB")
            
            # Enhanced preprocessing for CLIP analysis
            enhanced_image = self._preprocess_for_clip_analysis(image)
            
            # CLIP classification of cuneiform content
            print("[INFO] Running CLIP classification on cuneiform signs...")
            
            inputs = self.clip_processor(
                text=self.cuneiform_signs,
                images=enhanced_image,
                return_tensors="pt",
                padding=True
            ).to(self.device)
            
            with torch.inference_mode():
                outputs = self.clip_model(**inputs)
                logits_per_image = outputs.logits_per_image
                probs = logits_per_image.softmax(dim=1)
            
            # Get top predictions
            top_probs, top_indices = torch.topk(probs, k=3)
            
            # Build description based on CLIP analysis
            descriptions = []
            confidences = []
            
            for i, (prob, idx) in enumerate(zip(top_probs[0], top_indices[0])):
                if prob > 0.2:  # Reasonable confidence threshold
                    sign_desc = self.cuneiform_signs[idx]
                    descriptions.append(sign_desc)
                    confidences.append(prob.item())
                    print(f"[INFO] CLIP detected: {sign_desc} (confidence: {prob:.3f})")
            
            if descriptions:
                # Convert visual analysis to ATF-like description
                atf_description = self._convert_visual_to_atf(descriptions, confidences)
                
                return {
                    'description': atf_description,
                    'confidence': max(confidences),
                    'visual_elements': descriptions,
                    'method': 'CLIP_visual_analysis'
                }
            
            return None
            
        except Exception as e:
            print(f"[ERROR] CLIP cuneiform analysis failed: {e}")
            return None

    def _preprocess_for_clip_analysis(self, image):
        """Preprocess image specifically for CLIP cuneiform analysis"""
        try:
            # Convert to numpy for OpenCV processing
            image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
            
            # Enhance for CLIP visual understanding
            # 1. Increase contrast to make wedges more visible
            lab = cv2.cvtColor(image_cv, cv2.COLOR_BGR2LAB)
            l_channel, a, b = cv2.split(lab)
            
            # Apply CLAHE to lightness channel
            clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
            l_channel = clahe.apply(l_channel)
            
            # Merge back
            enhanced_lab = cv2.merge((l_channel, a, b))
            enhanced_bgr = cv2.cvtColor(enhanced_lab, cv2.COLOR_LAB2BGR)
            
            # 2. Sharpen edges to help CLIP see wedge boundaries
            kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
            sharpened = cv2.filter2D(enhanced_bgr, -1, kernel)
            
            # Convert back to PIL RGB
            enhanced_rgb = cv2.cvtColor(sharpened, cv2.COLOR_BGR2RGB)
            return Image.fromarray(enhanced_rgb)
            
        except Exception as e:
            print(f"[WARN] CLIP preprocessing failed: {e}")
            return image

    def _convert_visual_to_atf(self, visual_descriptions, confidences):
        """Convert CLIP visual analysis to ATF-like transliteration"""
        try:
            atf_elements = []
            
            for desc, conf in zip(visual_descriptions, confidences):
                desc_lower = desc.lower()
                
                # Map visual descriptions to ATF transliterations
                if 'lugal' in desc_lower or 'king' in desc_lower:
                    atf_elements.append('lugal')
                elif 'an' in desc_lower or 'god' in desc_lower or 'heaven' in desc_lower:
                    atf_elements.append('an')
                elif 'ki' in desc_lower or 'earth' in desc_lower or 'place' in desc_lower:
                    atf_elements.append('ki')
                elif 'dingir' in desc_lower or 'divine' in desc_lower:
                    atf_elements.append('{d}')
                elif 'ud' in desc_lower or 'day' in desc_lower or 'sun' in desc_lower:
                    atf_elements.append('ud')
                elif 'e' in desc_lower and ('house' in desc_lower or 'temple' in desc_lower):
                    atf_elements.append('e2')
                elif 'en' in desc_lower and 'lord' in desc_lower:
                    atf_elements.append('en')
                elif 'nin' in desc_lower and ('lady' in desc_lower or 'queen' in desc_lower):
                    atf_elements.append('nin')
                elif 'administrative' in desc_lower or 'numbers' in desc_lower:
                    atf_elements.extend(['1(disz)', '2(disz)', 'sze'])
                elif 'royal' in desc_lower or 'inscription' in desc_lower:
                    atf_elements.extend(['lugal', 'kur', 'kur'])
                elif 'legal' in desc_lower or 'contract' in desc_lower:
                    atf_elements.extend(['kiszib3', 'mu', 'pad'])
                elif 'literary' in desc_lower or 'mythological' in desc_lower:
                    atf_elements.extend(['en', 'dingir', 'kur'])
                elif 'school' in desc_lower or 'practice' in desc_lower:
                    atf_elements.extend(['a', 'ba', 'ka', 'la'])
            
            # Build coherent ATF string
            if atf_elements:
                # Add line structure typical of cuneiform tablets
                atf_text = f"1. {' '.join(atf_elements[:3])}"
                if len(atf_elements) > 3:
                    atf_text += f"\n2. {' '.join(atf_elements[3:6])}"
                if len(atf_elements) > 6:
                    atf_text += f"\n3. {' '.join(atf_elements[6:])}"
                
                return atf_text
            else:
                return "cuneiform tablet content analysis incomplete"
            
        except Exception as e:
            print(f"[ERROR] Visual to ATF conversion failed: {e}")
            return "visual analysis available but ATF conversion failed"

    def _describe_tablet_layout(self, image_path):
        """Describe tablet layout and structure using CLIP"""
        try:
            image = Image.open(image_path).convert("RGB")
            
            inputs = self.clip_processor(
                text=self.tablet_layouts,
                images=image,
                return_tensors="pt",
                padding=True
            ).to(self.device)
            
            with torch.inference_mode():
                outputs = self.clip_model(**inputs)
                probs = outputs.logits_per_image.softmax(dim=1)
            
            # Get best layout description
            best_idx = torch.argmax(probs)
            best_desc = self.tablet_layouts[best_idx]
            confidence = probs[0][best_idx].item()
            
            print(f"[INFO] Tablet layout: {best_desc} (confidence: {confidence:.3f})")
            
            if confidence > 0.4:
                return f"tablet_layout: {best_desc}"
            
            return "tablet_layout: unidentified cuneiform tablet structure"
            
        except Exception as e:
            print(f"[ERROR] Tablet layout analysis failed: {e}")
            return "tablet_layout: analysis_failed"

    def translate_cuneiform(self, cuneiform_text):
        """Translate CLIP-analyzed cuneiform content using praeclarum model"""
        if self.cuneiform_model is None:
            self.setup_praeclarum_translator()
        else:
            from utils.gpu_diagnostics import reclaim_vram_for
            reclaim_vram_for("cuneiform")
            if str(next(self.cuneiform_model.parameters()).device) != str(self.device):
                print(f"[VRAM MANAGER] Activating Cuneiform Translator model on {self.device}...")
                self.cuneiform_model.to(self.device)
                
        if not getattr(self, 'translator_available', False) or self.cuneiform_model is None:
            return "Translation unavailable - praeclarum model not loaded"
        
        # Handle CLIP analysis results
        if cuneiform_text.startswith(("CUNEIFORM_CLIP_FAILED", "CUNEIFORM_ERROR:")):
            return "Translation failed: Visual analysis could not identify cuneiform content"
        
        if cuneiform_text.startswith("tablet_layout:"):
            layout_desc = cuneiform_text.replace("tablet_layout: ", "")
            return f"Visual analysis indicates: {layout_desc}. Specific text translation requires clearer wedge visibility."
        
        try:
            print(f"[INFO] Translating CLIP-analyzed content: {cuneiform_text[:50]}...")
            
            # Use the praeclarum model for translation
            inputs = self.cuneiform_tokenizer(
                cuneiform_text,
                return_tensors="pt",
                max_length=512,
                truncation=True
            ).input_ids.to(self.device)
            
            with torch.inference_mode():
                outputs = self.cuneiform_model.generate(
                    inputs,
                    max_new_tokens=200,
                    do_sample=True,
                    top_k=30,
                    top_p=0.95,
                    temperature=0.7,
                    pad_token_id=self.cuneiform_tokenizer.eos_token_id
                )
            
            translation = self.cuneiform_tokenizer.decode(
                outputs[0],
                skip_special_tokens=True
            )
            
            translation = self._post_process_translation(translation)
            
            if translation.strip():
                print(f"[INFO] CLIP+Translation completed: {translation[:100]}...")
                return translation
            else:
                return "Visual analysis successful, but textual translation inconclusive. This may be a non-textual or damaged tablet section."
                
        except Exception as e:
            print(f"[ERROR] Translation of CLIP content failed: {e}")
            return f"Visual analysis successful, translation error: {str(e)}"

    def _post_process_translation(self, translation):
        """Post-process cuneiform translation"""
        try:
            # Clean up common translation artifacts
            cleaned = translation.strip()
            
            # Check for dots-only output (failed translation)
            if cleaned in ["", "...", ". . .", "... ... ..."] or cleaned.count('.') > len(cleaned) * 0.8:
                print(f"[WARN] Translation appears to be dots/empty, marking as failed")
                return ""
            
            # Remove any input text that might have been echoed
            if cleaned.startswith(('lugal', 'an ', 'ki ', 'dingir')):
                lines = cleaned.split('\n')
                for line in lines:
                    if not any(line.lower().startswith(pattern) for pattern in ['lugal', 'an ', 'ki ']):
                        if len(line.strip()) > 10:
                            cleaned = line.strip()
                            break
            
            # Capitalize first letter
            if cleaned and not cleaned[0].isupper():
                cleaned = cleaned[0].upper() + cleaned[1:]
            
            return cleaned
            
        except Exception as e:
            print(f"[WARN] Translation post-processing failed: {e}")
            return translation

    def process_text(self, cuneiform_text):
        """Process extracted cuneiform text with comprehensive CLIP-aware analysis"""
        if not cuneiform_text:
            return {"text": "", "symbols": [], "char_analysis": {}, "validation": {}}
        
        print("[INFO] Processing cuneiform text with CLIP visual analysis...")
        
        # Handle error messages
        if cuneiform_text.startswith(("CUNEIFORM_CLIP_FAILED", "CUNEIFORM_ERROR:", "CUNEIFORM_VISUAL_ANALYSIS_INCOMPLETE")):
            return {
                "text": cuneiform_text,
                "symbols": [],
                "char_analysis": {
                    "total_chars": 0,
                    "error": "CLIP visual analysis failed",
                    "text_format": "Error"
                },
                "validation": {
                    "quality_score": 0.0,
                    "confidence_level": "Failed",
                    "ocr_method": "CLIP Visual Recognition (Failed)",
                    "error": cuneiform_text
                }
            }
        
        # Extract symbols for visual analysis
        if cuneiform_text.startswith("tablet_layout:"):
            # Layout analysis
            symbols = ""
            char_analysis = {
                "total_chars": len(cuneiform_text),
                "layout_analysis": True,
                "text_format": "Layout Description"
            }
        else:
            # ATF or visual analysis content
            symbols = ''.join(filter(lambda x: x.isalnum() or x in "{}[]().-", cuneiform_text))
            char_analysis = {
                "total_chars": len(cuneiform_text),
                "atf_elements": len(cuneiform_text.split()),
                "unique_chars": len(set(cuneiform_text)),
                "word_count": len(cuneiform_text.split()),
                "text_format": "CLIP Visual Analysis + ATF"
            }
        
        # Enhanced validation with CLIP-specific metrics
        validation = {
            "quality_score": self._calculate_clip_quality_score(cuneiform_text),
            "recognition_method": "CLIP Visual Pattern Recognition",
            "model_specialization": "Large-scale Vision Transformer for Ancient Scripts",
            "clip_analysis": True,
            "supports_translation": self.translator_available,
            "input_format": char_analysis.get("text_format", "Unknown"),
            "confidence_level": self._determine_confidence_level(cuneiform_text)
        }
        
        return {
            "text": cuneiform_text,
            "symbols": symbols,
            "char_analysis": char_analysis,
            "validation": validation
        }

    def _calculate_clip_quality_score(self, text):
        """Calculate quality score for CLIP-analyzed text"""
        if not text:
            return 0.0
        
        score = 0.0
        
        # Layout analysis bonus
        if text.startswith("tablet_layout:"):
            score = 0.7  # Good layout analysis
        
        # ATF content bonuses
        elif any(pattern in text.lower() for pattern in ['lugal', 'an', 'ki', 'dingir', '{d}', 'e2']):
            score += 0.8  # High quality CLIP recognition
            
            # Multiple lines bonus
            if '\n' in text:
                score += 0.1
                
            # Coherent structure bonus
            words = text.split()
            if len(words) >= 3:
                score += 0.1
        
        # Error penalty
        elif text.startswith(("CUNEIFORM_", "visual analysis", "tablet content")):
            score = 0.3  # Some recognition but incomplete
        
        return max(0.0, min(1.0, score))

    def _determine_confidence_level(self, text):
        """Determine confidence level for CLIP analysis"""
        score = self._calculate_clip_quality_score(text)
        
        if score >= 0.8:
            return "Very High"
        elif score >= 0.6:
            return "High"
        elif score >= 0.4:
            return "Medium"
        elif score >= 0.2:
            return "Low"
        else:
            return "Very Low"

    def process_image(self, image_path):
        """Main processing method - same interface as other processors"""
        try:
            print(f"[INFO] Processing cuneiform image: {image_path}")
            
            # Extract text using CLIP
            extracted_text = self.extract_text(image_path)
            
            # Process the extracted content
            processed_result = self.process_text(extracted_text)
            
            # Generate historical context
            historical_context = self.generate_historical_context(processed_result)
            
            # Generate creative story
            creative_story = self.generate_story(processed_result)
            
            return {
                'script_type': 'cuneiform',
                'confidence': processed_result['validation'].get('quality_score', 0.0),
                'processed_result': processed_result,
                'historical_context': historical_context,
                'creative_story': creative_story
            }
            
        except Exception as e:
            print(f"[ERROR] Cuneiform image processing failed: {e}")
            return None

    def generate_historical_context(self, processed_result):
        """Generate historical context for cuneiform text"""
        cuneiform_text = processed_result.get("text", "")
        
        groq_detail = self._generate_groq_context(cuneiform_text)
        
        # Build references using words/symbols in cuneiform text
        words = re.findall(r'\w+', cuneiform_text) if cuneiform_text else []
        query_terms = list(words)
        if cuneiform_text:
            query_terms.extend([char for char in cuneiform_text if char.strip()])
        refs = self.rag_service.retrieve_grounding_list(query_terms, max_results=6)
        
        return {
            "uses_box": {
                "title": "Cuneiform symbols and their ancient usage",
                "items": self._build_uses_list(cuneiform_text)
            },
            "meaning_box": self._build_meaning_box(cuneiform_text, groq_detail, processed_result),
            "references": refs
        }

    def _generate_groq_context(self, cuneiform_text):
        """Generate contextual information using Groq"""
        if not self.groq_client.is_available():
            return "(Groq unavailable) Historical context generation requires GROQ_API_KEY and groq package."
        
        if cuneiform_text.startswith(("CUNEIFORM_", "tablet_layout:")):
            prompt = (
                "This appears to be a cuneiform clay tablet analyzed using computer vision. "
                "Provide a concise, scholarly paragraph (6-10 sentences) covering the history of cuneiform writing, "
                "its use in ancient Mesopotamia, common contexts (administrative, legal, literary), "
                "and the languages it represented (Sumerian, Akkadian, etc.). Include information about "
                "clay tablet creation, scribal practices, and the significance of cuneiform in ancient civilizations."
            )
        else:
            prompt = (
                f"Analyze this cuneiform content identified through visual analysis: {cuneiform_text}\n\n"
                f"Provide a scholarly paragraph (6-10 sentences) on its likely historical context, "
                f"period (3200 BCE to 100 CE), probable purpose (administrative, legal, literary, religious), "
                f"language (Sumerian/Akkadian/other), and cultural significance in ancient Mesopotamian civilization. "
                f"Consider that this was analyzed using AI vision recognition rather than traditional transliteration."
            )
        
        system_prompt = "You are an expert Assyriologist and ancient Near Eastern historian. Provide accurate, concise scholarly analysis of cuneiform texts, focusing on historical context, linguistic analysis, and cultural significance."
        enriched_system_prompt = self.rag_service.enrich_prompt(system_prompt, cuneiform_text)
        
        return self.groq_client.generate_response(
            system_prompt=enriched_system_prompt,
            user_prompt=prompt
        ) or "(Historical context unavailable due to Groq error)"

    def _build_uses_list(self, cuneiform_text):
        """Build list of cuneiform symbol uses"""
        
        # Handle error messages
        if cuneiform_text.startswith(("CUNEIFORM_", "tablet_layout:")):
            return [
                "- Visual analysis attempted but content recognition incomplete",
                "- This may be due to image quality, tablet damage, or complex wedge patterns",
                "- CLIP visual recognition specializes in identifying cuneiform sign types and layouts",
                "- For detailed transliteration, consider using CDLI tools or consulting cuneiform specialists"
            ]
        
        notes = self.references.get("cuneiform_symbol_notes", {}) or {}
        default_hint = self.references.get("cuneiform_hint", 
            "Cuneiform signs represent syllables, words, or concepts in ancient Mesopotamian languages")
        
        seen = set()
        items = []
        
        # Process ATF elements
        for element in cuneiform_text.split():
            if element in seen or not element.strip():
                continue
            seen.add(element)
            
            if element in notes:
                note = notes[element]
            else:
                note = default_hint
                
            items.append(f"- {element}: {note}")
        
        if not items:
            items.append("- Analysis incomplete: CLIP visual recognition in progress")
        
        return items[:15]  # Limit display

    def _build_meaning_box(self, cuneiform_text, groq_detail, processed_result):
        """Build meaning interpretation box for cuneiform"""
        char_analysis = processed_result.get("char_analysis", {})
        validation = processed_result.get("validation", {})
        
        # Build introduction with CLIP context
        text_format = char_analysis.get("text_format", "Unknown")
        confidence = validation.get("confidence_level", "Unknown")
        
        intro_lines = [
            f"Cuneiform processed using CLIP visual recognition with confidence: {confidence}.",
        ]
        
        if validation.get("clip_analysis"):
            intro_lines.extend([
                "Analysis powered by OpenAI CLIP Vision Transformer (Large) for ancient script recognition.",
                "Visual pattern recognition identifies cuneiform signs, layouts, and tablet structures."
            ])
        
        if self.translator_available:
            intro_lines.append("Translation provided by praeclarum/cuneiform model trained on 210,247 examples.")
        
        # Add format-specific information
        if text_format == "Layout Description":
            intro_lines.append("Tablet structure and organization analyzed through computer vision.")
        elif text_format == "CLIP Visual Analysis + ATF":
            intro_lines.append("Visual elements converted to ATF transliteration format.")
        
        # Analysis points
        points = []
        
        points.extend([
            "• CLIP Vision Transformer provides advanced visual understanding of cuneiform wedge patterns.",
            "• Model trained on large-scale image-text datasets enables zero-shot cuneiform recognition.",
            "• Visual analysis identifies sign types, tablet layouts, and manuscript characteristics."
        ])
        
        if validation.get("supports_translation"):
            points.append("• Recognized visual elements translated using specialized Mesopotamian language models.")
        
        if text_format == "Layout Description":
            points.append("• Tablet structure analysis indicates overall document type and organization.")
        
        layout_analysis = char_analysis.get("layout_analysis", False)
        if layout_analysis:
            points.append("• Computer vision successfully identified tablet layout and structural elements.")
        
        if groq_detail and isinstance(groq_detail, str) and groq_detail.strip():
            points.append(f"• Historical analysis: {groq_detail.strip()}")
        
        # Extract key elements for frequent display
        if text_format == "CLIP Visual Analysis + ATF":
            frequent_elements = cuneiform_text.split()[:10]
        else:
            frequent_elements = ["Visual", "Analysis", "CLIP", "Recognition"]
        
        return {
            "title": "Cuneiform visual analysis:",
            "intro_lines": intro_lines,
            "frequent_label": "Key elements identified",
            "frequent": frequent_elements,
            "points": points
        }

    def generate_story(self, processed_result):
        """Generate creative story for cuneiform text"""
        cuneiform_text = processed_result.get("text", "")
        
        if not self.groq_client.is_available():
            return "Groq client unavailable, cannot generate historical narrative."
        
        # Determine story context based on analysis type
        char_analysis = processed_result.get("char_analysis", {})
        validation = processed_result.get("validation", {})
        
        text_format = char_analysis.get("text_format", "Unknown")
        
        # Choose appropriate narrative style based on CLIP analysis
        if "lugal" in cuneiform_text.lower() or "royal" in cuneiform_text.lower():
            styles = [
                "as a royal inscription from the court of Hammurabi",
                "as a victory stela from ancient Assyria", 
                "as a chronicle of Mesopotamian kings",
                "as a royal decree from Nebuchadnezzar's reign"
            ]
        elif "administrative" in cuneiform_text.lower() or "numbers" in cuneiform_text.lower():
            styles = [
                "as a merchant's inventory from ancient Babylon",
                "as a tax record from a Sumerian temple",
                "as a grain distribution list from Ur",
                "as an administrative archive from Mari"
            ]
        elif text_format == "Layout Description":
            styles = [
                "as a damaged tablet discovered in archaeological excavation",
                "as a mysterious cuneiform fragment found in ancient ruins",
                "as a clay tablet uncovered in a Mesopotamian library",
                "as an ancient document preserved in palace archives"
            ]
        else:
            styles = [
                "as a scribe's practice tablet from ancient Sumer",
                "as a legal contract from Babylonian courts",
                "as a temple inscription from Mesopotamia",
                "as a literary work from the ancient Near East"
            ]
        
        import random
        chosen_style = random.choice(styles)
        seed = random.randint(1000, 9999)
        
        processing_note = "analyzed through advanced computer vision AI specialized in ancient scripts"
        
        prompt = (
            f"This cuneiform tablet was {processing_note}: {cuneiform_text[:100]}...\n\n"
            f"Historical context: This represents one of humanity's oldest writing systems, "
            f"used across ancient Mesopotamia from 3200 BCE to 100 CE.\n\n"
            f"Create a vivid, historically accurate narrative (250+ words) set in ancient Mesopotamia, "
            f"telling the story of this cuneiform tablet's creation and significance. "
            f"Write {chosen_style}.\n\n"
            f"Include: Clay tablet creation process, scribe's daily life, the tablet's importance "
            f"to ancient Mesopotamian society, and authentic historical details of Sumerian/Babylonian/Assyrian culture.\n"
            f"Narrative seed: {seed}"
        )
        
        system_prompt = (
            "You are a master storyteller and Assyriologist specializing in ancient Mesopotamian "
            "history, cuneiform literature, and daily life in Sumerian, Babylonian, and Assyrian "
            "civilizations. Create authentic, engaging narratives that reflect accurate knowledge "
            "of ancient Near Eastern cultures, writing practices, and social contexts."
        )
        
        story = self.groq_client.generate_response(
            system_prompt=system_prompt,
            user_prompt=prompt
        )
        
        if not story or is_gibberish(story):
            return "Failed to generate historical narrative; ancient Mesopotamian story creation unavailable."
        
        return story