import gradio as gr import pytesseract from PIL import Image import cv2 import numpy as np import tempfile import os import io # LSB Digital OCR Service # Layanan OCR untuk Formulir Laporan Sumber Bahaya # https://huggingface.co/spaces/Unlimitedlevel19/LSB # Fungsi untuk meningkatkan kualitas gambar def preprocess_image(image): # Konversi ke array numpy img = np.array(image) # Konversi ke grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Thresholding adaptif thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) # Noise removal kernel = np.ones((1, 1), np.uint8) opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) return opening # Fungsi untuk mendeteksi tanda centang pada kotak def detect_checkboxes(image, orig_image): # Deteksi kotak-kotak (checkbox) img = np.array(image) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Threshold gambar untuk mendapatkan area checkbox _, thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV) # Deteksi kontur contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Filter kontur yang mungkin merupakan checkbox checkboxes = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) # Filter berdasarkan ukuran if 10 < w < 50 and 10 < h < 50: # Periksa apakah checkbox dicentang roi = orig_image[y:y+h, x:x+w] gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) _, binary_roi = cv2.threshold(gray_roi, 150, 255, cv2.THRESH_BINARY_INV) non_zero_pixels = cv2.countNonZero(binary_roi) ratio = non_zero_pixels / (w * h) # Jika rasio piksel non-zero cukup tinggi, kemungkinan dicentang is_checked = ratio > 0.1 checkboxes.append((x, y, w, h, is_checked)) # Mendeteksi jenis pengamatan berdasarkan posisi checkbox jenis_pengamatan = { 'Unsafe Condition': False, 'Unsafe Action': False, 'Intervensi': False } # Cari checkbox yang dicentang dan tentukan posisinya untuk jenis pengamatan for (x, y, w, h, is_checked) in checkboxes: # Atur kondisi berdasarkan posisi checkbox pada form standard LSB if is_checked: if x < img.shape[1] // 3: # Checkbox di posisi pertama jenis_pengamatan['Unsafe Condition'] = True elif img.shape[1] // 3 < x < 2 * img.shape[1] // 3: # Checkbox di posisi kedua jenis_pengamatan['Unsafe Action'] = True else: # Checkbox di posisi ketiga jenis_pengamatan['Intervensi'] = True return jenis_pengamatan # Fungsi untuk memparse teks dari form LSB def parse_form_text(text): lines = text.split('\n') data = { 'nama_pelapor': '', 'posisi_jabatan': '', 'lokasi_kejadian': '', 'tanggal_waktu': '', 'uraian_pengamatan': '', 'tindakan_intervensi': '', } # Cari setiap field dalam teks current_field = None for i, line in enumerate(lines): # Deteksi field berdasarkan kata kunci if 'NAMA PELAPOR' in line: current_field = 'nama_pelapor' if i+1 < len(lines) and lines[i+1].strip(): data[current_field] = lines[i+1].strip() elif 'POSISI' in line or 'JABATAN' in line: current_field = 'posisi_jabatan' if i+1 < len(lines) and lines[i+1].strip(): data[current_field] = lines[i+1].strip() elif 'LOKASI' in line: current_field = 'lokasi_kejadian' if i+1 < len(lines) and lines[i+1].strip(): data[current_field] = lines[i+1].strip() elif 'TANGGAL' in line or 'WAKTU' in line: current_field = 'tanggal_waktu' if i+1 < len(lines) and lines[i+1].strip(): data[current_field] = lines[i+1].strip() elif 'URAIAN' in line and 'PENGAMATAN' in line: current_field = 'uraian_pengamatan' # Ambil beberapa baris untuk uraian for j in range(i+1, min(i+4, len(lines))): if lines[j].strip() and not any(keyword in lines[j] for keyword in ['INTERVENSI', 'TINDAKAN', 'SARAN', 'PELAPOR']): data[current_field] += ' ' + lines[j].strip() elif ('TINDAKAN' in line and 'INTERVENSI' in line) or 'PERBAIKAN' in line: current_field = 'tindakan_intervensi' # Ambil beberapa baris untuk tindakan for j in range(i+1, min(i+4, len(lines))): if lines[j].strip() and not any(keyword in lines[j] for keyword in ['PELAPOR', 'HSE', 'PENERIMA']): data[current_field] += ' ' + lines[j].strip() # Bersihkan teks for key in data: data[key] = data[key].strip() return data # Fungsi utama untuk OCR def perform_ocr(image): try: # Jika image adalah PIL.Image, konversi ke numpy array if not isinstance(image, np.ndarray): image = np.array(image) # Simpan gambar ke file temporari with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp: image_path = temp.name img_pil = Image.fromarray(image) img_pil.save(image_path) # Preprocess gambar untuk OCR preprocessed = preprocess_image(img_pil) cv2.imwrite(image_path + '_processed.jpg', preprocessed) # Lakukan OCR pada gambar yang telah diproses text = pytesseract.image_to_string(Image.open(image_path + '_processed.jpg'), lang='ind') # Hapus file temporari os.unlink(image_path) os.unlink(image_path + '_processed.jpg') # Lakukan juga deteksi checkbox jenis_pengamatan = detect_checkboxes(img_pil, image) # Parse teks hasil OCR menjadi data terstruktur data = parse_form_text(text) # Tambahkan hasil deteksi checkbox ke data data['jenis_pengamatan'] = [] for jenis, checked in jenis_pengamatan.items(): if checked: data['jenis_pengamatan'].append(jenis) # Gabungkan menjadi string if data['jenis_pengamatan']: data['jenis_pengamatan'] = ', '.join(data['jenis_pengamatan']) else: data['jenis_pengamatan'] = '' return data except Exception as e: print(f"Error in OCR: {e}") return { 'error': str(e), 'nama_pelapor': '', 'posisi_jabatan': '', 'lokasi_kejadian': '', 'tanggal_waktu': '', 'uraian_pengamatan': '', 'tindakan_intervensi': '', 'jenis_pengamatan': '' } # Buat interface Gradio sederhana def process_image(img): if img is None: return "No image uploaded" # Proses OCR result = perform_ocr(img) # Return hasil sebagai string untuk ditampilkan return str(result) # Buat interface yang sangat sederhana untuk API demo = gr.Interface( fn=process_image, inputs=gr.Image(type="pil"), outputs="text", title="LSB Form OCR API", description="Upload gambar formulir LSB untuk ekstraksi data otomatis", examples=[], cache_examples=False, ) # Launch dengan API enabled demo.launch(share=True)