i want to add to this upload images from digitalocean and do the images_process to all the images in good way "import os
import cv2
import io
import numpy as np
import base64

from character import classify_character

from fastapi import FastAPI, File, UploadFile, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from detectron2.config import get_cfg
from detectron2.engine import DefaultPredictor
from detectron2.utils.visualizer import Visualizer
from PIL import Image
import logging
from tqdm import tqdm

from functools import lru_cache

import uvicorn

app = FastAPI(title="Image Segmentation Service")

BASE_DIR = os.getcwd()
MODEL_DIR = os.path.join(BASE_DIR, "models")
MODELS = {}  # We will store our models here


##########################################################################
# the vg model
#from keras.models import load_model
from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
import os
import tensorflow as tf

from tensorflow.keras import layers
from tensorflow.keras import Model
from tensorflow.keras.applications.inception_v3 import InceptionV3
from tensorflow.keras.optimizers import RMSprop

from tensorflow.keras.models import load_model
import base64
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'

# Load the VGG model
model_path='weights/vgg.h5'

model5 = load_model(model_path)


# Define the document lines
document_lines = ['שמעישראליהוהאלהינויהוהאחדואהבתאת', 
                'יהוהאלהיךבכללבבךובכלנפשךובכלמאדךוהיו', 
                'הדבריםהאלהאשראנכימצוךהיוםעללבבךושננתם',
                'לבניךודברתבםבשבתךבביתךובלכתךבדרך', 
                'ובשכבךובקומךוקשרתםלאותעלידךוהיולטטפת', 
                'ביןעיניךוכתבתםעלמזזותביתךובשעריך', 
                'והיהאםשמעתשמעואלמצותיאשראנכי', 
                'מצוהאתכםהיוםלאהבהאתיהוהאלהיכםולעבדו',
                'בכללבבכםובכלנפשכםונתתימטרארצכםבעתו', 
                'יורהומלקושואספתדגנךותירשךויצהרךונתתי', 
                'עשבבשדךלבהמתךואכלתושבעתהשמרולכם', 
                'פןיפתהלבבכםוסרתםועבדתםאלהיםאחרים', 
                'והשתחויתםלהםוחרהאףיהוהבכםועצראת', 
                'השמיםולאיהיהמטרוהאדמהלאתתןאתיבולה', 
                'ואבדתםמהרהמעלהארץהטבהאשריהוהנתןלכם',
                'ושמתםאתדבריאלהעללבבכםועלנפשכםוקשרתם', 
                'אתםלאותעלידכםוהיולטוטפתביןעיניכםולמדתם', 
                'אתםאתבניכםלדברבםבשבתךבביתךובלכתך', 
                'בדרךובשכבךובקומךוכתבתםעלמזוזותביתך', 
                'ובשעריךלמעןירבוימיכםוימיבניכםעלהאדמה',
                'אשרנשבעיהוהלאבתיכםלתתלהםכימיהשמים', 
                'עלהארץ']

hebrew_font_path = "./ocr_utilities/Nehama.ttf"
hebrew_font = ImageFont.truetype(hebrew_font_path, size=30)

import cv2
import numpy as np
import tensorflow as tf


#classifier model prediction
def classify_character2(crop, model):
  # Define the original class dictionary
  #class_dict = {'1': 0, '10': 1, '11': 2, '12': 3, '13': 4, '14': 5, '15': 6, '16': 7, '17': 8, '18': 9, '19': 10, '2': 11, '20': 12, '21': 13, '22': 14, '23': 15, '24': 16, '25': 17, '26': 18, '27': 19, '3': 20, '4': 21, '5': 22, '6': 23, '7': 24, '8': 25, '9': 26}
  class_dict = {'א': 0, 'י': 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}
  resized_image = cv2.resize(crop, (128, 128))
  resized_image = resized_image/255.0
  resized_image = np.expand_dims(resized_image, axis=0)
  pred = model.predict(resized_image,verbose =0)[0]
  # pred_prob = max(pred)
  # pred_prob_list = list(pred)
  pred_class = np.argmax(pred)
  # Map the predicted classes using the new class dictionary #check if this could be simplified.
  output_labels = {v: k for k, v in class_dict.items()}
  predicted_class_value = output_labels.get(pred_class)
  return predicted_class_value



###########################################################################


class FastAPIModel:
    @staticmethod
    def get_cfg(model_name: str):
        cfg = get_cfg()
        cfg.merge_from_file("./detectron2_repo/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")
        cfg.MODEL.DEVICE = "cpu"
        cfg.OUTPUT_DIR = MODEL_DIR  
        cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, f"{model_name}.pth")
        os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)
        cfg.DATALOADER.NUM_WORKERS = 2
        cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5
        cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 128   
        cfg.MODEL.ROI_HEADS.NUM_CLASSES = 3
        cfg.SOLVER.IMS_PER_BATCH = 2
        cfg.SOLVER.BASE_LR = 0.02
        cfg.SOLVER.MAX_ITER = 10000
        return cfg

    def __init__(self, model_name: str):
        self.cfg = self.get_cfg(model_name)
        self.predictor = DefaultPredictor(self.cfg)

    def detect_objects_and_visualize(self, image_array):
        try:
            outputs = self.predictor(image_array)
            v = Visualizer(image_array[:, :, ::-1], scale=0.8)
            out = v.draw_instance_predictions(outputs["instances"].to("cpu"))
            return out.get_image()[:, :, ::-1]

        except Exception as e:
            logging.error(f"Error during object detection: {e}")
            return {"error": f"Error during prediction: {e}"}

@lru_cache
def available_models():
    return [model.split(".")[0] for model in os.listdir(MODEL_DIR) if model.endswith(".pth")]

def load_models():
    model_names = available_models()
    for model_name in model_names:
        MODELS[model_name] = FastAPIModel(model_name)

#load the templates
templates = Jinja2Templates(directory="templates")
# Load models on startup
load_models()


def encode_image_to_base64(image: np.ndarray) -> str:
    """Encode a numpy image array to base64 string"""
    success, buffer = cv2.imencode('.jpg', image)
    if not success:
        raise ValueError("Could not encode image to JPEG format")
    return base64.b64encode(buffer).decode("utf-8")


def run_prediction(model_name: str, image):
    try:
        model = MODELS[model_name]  # Use already loaded model based on provided name
        outputs = model.predictor(image)
        instances = outputs["instances"]
        v = Visualizer(image[:, :, ::-1], scale=0.8)
        processed_image = v.draw_instance_predictions(outputs["instances"].to("cpu")).get_image()[:, :, ::-1]

        return processed_image, instances

    except Exception as e:
        logging.error(f"Error during {model_name} prediction: {e}")
        return "Error during prediction", None



@app.post('/process_image', response_class=HTMLResponse, status_code=200)
async def process_image(request: Request, file: UploadFile = File(...)):
    try:
        image_contents = await file.read()
        image = Image.open(io.BytesIO(image_contents))
        image_array = np.array(image)
        visualised_origine, instances_lines = run_prediction('model_final_lines', image_array)

        results = []

        for i, instance_idx in tqdm(enumerate(range(len(instances_lines))), desc="Processing Instances"):
            if instances_lines.pred_classes[instance_idx] != 0:
                continue
            polygon = instances_lines.pred_masks[instance_idx].cpu().numpy().astype(np.uint8)
            contours_lines, _ = cv2.findContours(polygon, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            if not contours_lines:
                continue

            for contour_idx, contour_line in enumerate(tqdm(contours_lines, desc=f"Processing contours_lines for Instance {i}")):
                x, y, w, h = cv2.boundingRect(contour_line)
                area = w * h
                # Add a condition based on width and height
                if w < 1500 and h < 80:
                    continue

                mask = np.zeros_like(image_array, dtype=np.uint8)
                cv2.drawContours(mask, [contour_line], contourIdx=-1, color=(255,) * mask.shape[2], thickness=cv2.FILLED)
                masked_image = cv2.bitwise_and(image_array, mask)
                cropped_image = masked_image[y:y + h, x:x + w]
                
                # Run prediction for the letter
                letter_prediction, instance_letter = run_prediction('model_letter', cropped_image)
                letters = []
                for j, instance_jdx in enumerate(range(len(instance_letter))):
                    if instance_letter.pred_classes[j] != 1:
                        continue
                    polygon = instance_letter.pred_masks[j].cpu().numpy().astype(np.uint8)
                    contours_letters, _ = cv2.findContours(polygon, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
                    if not contours_letters:
                        continue




                    # Define the desired size for all ROIs
                    background_size = (128, 128)
                    
                    # Define the background color
                    background_color = (255, 255, 255)  # White background in BGR format


                    for contour_idx, contour_letter in enumerate(contours_letters):
                        # Approximate the contour with a rectangle
                        epsilon = 0.04 * cv2.arcLength(contour_letter, True)
                        approx = cv2.approxPolyDP(contour_letter, epsilon, True)
                        x, y, w, h = cv2.boundingRect(approx)

                        # Adjust bounding box by 5 pixels
                        x -= 5
                        y -= 5
                        w += 2 * 5
                        h += 2 * 5

                        # Calculate the width and height of the ROI
                        roi_width = min(w, background_size[0])
                        roi_height = min(h, background_size[1])

                        # Take the crop
                        roi_cropped = cropped_image[y:y + roi_height, x:x + roi_width]

                        # Check if the dimensions of the cropped ROI are non-zero
                        if roi_cropped.shape[0] != 0 and roi_cropped.shape[1] != 0:
                            # Calculate the maximum resizing factor to fit the ROI within the background size
                            resize_factor_w = min(2, background_size[0] / roi_cropped.shape[1])
                            resize_factor_h = min(2, background_size[1] / roi_cropped.shape[0])
                            resize_factor = min(resize_factor_w, resize_factor_h)

                            # Resize the cropped ROI by the calculated factor
                            roi_resized = cv2.resize(roi_cropped, (int(resize_factor * roi_cropped.shape[1]), int(resize_factor * roi_cropped.shape[0])))

                            # Create a blank background image of the desired size
                            background = np.full((background_size[1], background_size[0], 3), background_color, dtype=np.uint8)

                            # Calculate the position to place the cropped ROI within the background image
                            offset_x = (background_size[0] - roi_resized.shape[1]) // 2
                            offset_y = (background_size[1] - roi_resized.shape[0]) // 2

                            # Overlay the resized ROI onto the background image
                            background[offset_y:offset_y + roi_resized.shape[0], offset_x:offset_x + roi_resized.shape[1]] = roi_resized
                            


                            # Define the black color range
                            black_lower = (0, 0, 0) 
                            black_upper = (2, 2, 2)  

                            # Find black pixels and replace them with white
                            black_mask = cv2.inRange(background, black_lower, black_upper)
                            background[black_mask > 0] = [255, 255, 255]

                            # Apply a binary threshold to create a binary image
                            _, binary_image = cv2.threshold(background, 127, 255, cv2.THRESH_BINARY)

                            letter_text = classify_character2(background, model5)
                            letter_prediction_binary = classify_character2(binary_image, model5)
                            print("*************")
                            print(f"Letter not preprocessed: {letter_text}")
                            print(f"letter preprocessed: {letter_prediction_binary}")
                            print("/////////////////")
                            letters.append((background, letter_prediction_binary))

                        else:
                            print("Warning: Cropped ROI dimensions are zero, skipping processing for this contour.")


                # Encode the letters
                encoded_letters = [encode_image_to_base64(letter[0]) for letter in letters]

                # Add the letters to results
                # Append the result to the results list
                results.append({
                    'original': encode_image_to_base64(cropped_image),
                    'letter_prediction': encode_image_to_base64(letter_prediction),
                    'letters': encoded_letters,  # Assuming you have encoded the letter images
                    'contour_idx': contour_idx,
                    'instance_idx': i,
                    'area': area,
                    'letter_texts': [letter_text for _, letter_text in letters]  # Assuming 'letters' is a list of tuples (background_image, letter_text)
                })

        return templates.TemplateResponse(
            "result_template.html",
            {
                "request": request,
                "code": 200,
                "encoded_image": encode_image_to_base64(image_array),
                "encoded_predicted": encode_image_to_base64(visualised_origine),
                "message": "Success",
                "results": results
            }
        )
    except Exception as e:
        logging.error(f"Error processing image: {e}")
        return templates.TemplateResponse(
            "result_template.html",
            {
                "request": request,
                "code": 500,
                "message": f"Error: {e}",
            }
        )  # a more meaningful response can be used
    

@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    available_model_names = MODELS.keys()  # Get model names from MODELS dictionary
    print(f"Available Models: {available_model_names}") 
    # It's good practice not to print output directly in production. Use a logger instead.
    return templates.TemplateResponse("upload_form.html", {"request": request, "model_names": available_model_names})


if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)