I have a code understand the code. Basically which is reading csv file which is ocr textract of an invoice which is in image format. That contains these columns (page_num,block_num,line_num,word_num,left,right,top,bottom,width,height,conf,text,image_height,image_width,skewness,orientation)

Modify the code to calculate the conf (confidence score) of bounding boxes which is the output of process_single_token_entity and process_multi_token_enetity. In case of multi token entity calculate the combined or average value of all the bounding box. 

import cv2
import pandas as pd
import json
from thefuzz import fuzz
from itertools import product

class BoundingBoxFinder:
    def __init__(self):
        print("Initializing used_bounding_boxes")
        self.used_bounding_boxes = {}


    def preprocess_entity(self, entity):
        try:
            token = entity.replace(",", "").strip()
            return token
        except:
            pass

    def calculate_proximity_score(self, box_a, box_b):
        vertical_overlap = max(0, min(box_a["bottom"], box_b["bottom"]) - max(box_a["top"], box_b["top"]))
        vertical_distance = 0 if vertical_overlap > 0 else min(abs(box_a["top"] - box_b["bottom"]), abs(box_a["bottom"] - box_b["top"]))
        horizontal_overlap = max(0, min(box_a["right"], box_b["right"]) - max(box_a["left"], box_b["left"]))
        horizontal_distance = 0 if horizontal_overlap > 0 else abs(box_a["right"] - box_b["left"])
        return horizontal_distance + 2 * vertical_distance

    def is_nearby(self, box_a, box_b, max_line_difference=5, max_distance=100):
        return self.calculate_proximity_score(box_a, box_b) <= max_distance + 2 * max_line_difference

    def merge_boxes(self, boxes):
        min_left = min(box["left"] for box in boxes)
        max_right = max(box["right"] for box in boxes)
        min_top = min(box["top"] for box in boxes)
        max_bottom = max(box["bottom"] for box in boxes)
        return {"left": min_left, "right": max_right, "top": min_top, "bottom": max_bottom}

    def find_potential_matches(self, dataframe, token, threshold=75):
        potential_matches = []
        for _, row in dataframe.iterrows():
            ocr_text = self.preprocess_entity(row["text"])
            score = fuzz.ratio(token, ocr_text)
            if score > threshold:
                potential_matches.append({
                    "box": {"left": row["left"], "right": row["right"], "top": row["top"], "bottom": row["bottom"]},
                    "score": score
                })
        return potential_matches

    def find_best_sequence_heuristic(self, matches_list):
        if not matches_list or len(matches_list[0]) == 0:
            return []
        best_sequence = [min(matches_list[0], key=lambda match: match["score"])]
        for next_matches in matches_list[1:]:
            current_box = best_sequence[-1]["box"]
            next_best_match = min(next_matches, key=lambda match: self.calculate_proximity_score(current_box, match["box"]))
            best_sequence.append(next_best_match)
        return best_sequence

    def process_single_token_entity(self, dataframe, entity, threshold=75):
        best_match = None
        best_score = threshold
        entity = self.preprocess_entity(entity)

        if entity not in self.used_bounding_boxes:
            self.used_bounding_boxes[entity] = []

        for _, row in dataframe.iterrows():
            ocr_text = self.preprocess_entity(row["text"])
            score = fuzz.ratio(entity, ocr_text)
            current_box = {"left": row["left"], "right": row["right"], "top": row["top"], "bottom": row["bottom"]}
            if score > best_score and current_box not in self.used_bounding_boxes[entity]:
                best_score = score
                best_match = current_box

        if best_match:
            self.used_bounding_boxes[entity].append(best_match)

        return best_match

    def box_overlap(self, box1, box2):
        """Check if there"s any overlap in any coordinate between two boxes."""
        return box1["left"] == box2["left"] or box1["right"] == box2["right"]

    def all_boxes_unique(self, sequence_boxes, used_boxes):
        """Ensure no part of the boxes in sequence_boxes overlaps with any box in used_boxes."""
        for seq_box in sequence_boxes:
            for used_box in used_boxes:
                if self.box_overlap(seq_box, used_box):
                    return False
        return True

    def get_next_best_sequence(self, all_potential_matches, previous_matches, entity):
        """
        Try to find the next best sequence of matches that hasn"t used any part of the bounding boxes.
        """
        # Flatten the list of used boxes for easier comparison.
        used_boxes = [box for sequence in previous_matches.get(entity, []) for box in sequence]

        for sequence in product(*all_potential_matches):
            sequence_boxes = [match["box"] for match in sequence]

            if self.all_boxes_unique(sequence_boxes, used_boxes):
                return sequence  # Found a sequence where no box part has been used before

        return None  # No unique sequence found

    def process_multi_token_entity(self, dataframe, entity, threshold=75, max_distance=100, max_line_difference=3):
        tokens = entity.split()
        all_potential_matches = [self.find_potential_matches(dataframe, token, threshold) for token in tokens]

        if not all(matches for matches in all_potential_matches):
            return None

        if entity not in self.used_bounding_boxes:
            self.used_bounding_boxes[entity] = []

        previous_matches = self.used_bounding_boxes.get(entity, [])

        all_sequences = list(product(*all_potential_matches))  # Create all possible sequences
        valid_sequences = []  # List to hold sequences that meet the is_nearby condition

        for sequence in all_sequences:
            sequence_boxes = [match["box"] for match in sequence]
            sequence_is_valid = True
            for i in range(len(sequence_boxes) - 1):
                if not self.is_nearby(sequence_boxes[i], sequence_boxes[i + 1], max_line_difference, max_distance):
                    sequence_is_valid = False
                    break
            if sequence_is_valid:
                valid_sequences.append(sequence)

        # Sort valid sequences by their cumulative proximity score, to prioritize those with boxes closer together
        valid_sequences = sorted(valid_sequences, key=lambda seq: sum(self.calculate_proximity_score(seq[i]["box"], seq[i+1]["box"]) for i in range(len(seq) - 1)))

        next_best_sequence = None
        for sequence in valid_sequences:
            sequence_boxes = [match["box"] for match in sequence]
            if self.all_boxes_unique(sequence_boxes, [box for sublist in previous_matches for box in sublist]):
                next_best_sequence = sequence
                break

        if next_best_sequence:
            new_boxes_sequence = [match["box"] for match in next_best_sequence]
            merged_box = self.merge_boxes(new_boxes_sequence)
            self.used_bounding_boxes[entity].append(new_boxes_sequence)
            return merged_box

        return None

    def draw_bounding_boxes(self, image_path, bounding_boxes, entity_names):
        image = cv2.imread(image_path)
        font = cv2.FONT_HERSHEY_SIMPLEX
        for box, name in zip(bounding_boxes, entity_names):
            if box:
                cv2.rectangle(image, (box["left"], box["top"]), (box["right"], box["bottom"]), (0, 255, 0), 2)
                cv2.putText(image, name, (box["left"], max(box["top"] - 10, 0)), font, 0.5, (0, 0, 255), 2)
        cv2.imwrite("annotated_imagecls.jpg", image)

    def process_data(self, json_path, csv_path, image_path):
        with open(json_path, "r") as f:
            data = json.load(f)
        dataframe = pd.read_csv(csv_path)
        bounding_boxes = []
        entity_names = []

        # Existing processing for non-special sections
        special_sections = ["amounts_and_tax", "Payment Details"]  # Define special handling cases here

        for section in ["invoice_details", "Payment Details", "amounts_and_tax"]:
            entities = data.get(section, {})

            # Check if the current section needs special handling
            if section not in special_sections:
                for entity_name, entity_value in entities.items():
                    entity_value_no_comma = self.preprocess_entity(entity_value)
                    if " " in entity_value_no_comma:
                        box = self.process_multi_token_entity(dataframe, entity_value_no_comma)
                    else:
                        box = self.process_single_token_entity(dataframe, entity_value_no_comma)
                    if box:
                        bounding_boxes.append(box)
                        entity_names.append(entity_name)
            else:
                # Special handling for "amounts_and_tax" section
                reversed_dataframe = dataframe.iloc[::-1].reset_index(drop=True)  # Reverse the dataframe
                for entity_name, entity_value in entities.items():
                    entity_value_no_comma = self.preprocess_entity(entity_value)
                    if " " in entity_value_no_comma:
                        # Use the reversed_dataframe for multi-token entities
                        box = self.process_multi_token_entity(reversed_dataframe, entity_value_no_comma)
                    else:
                        # Use the reversed_dataframe for single-token entities
                        box = self.process_single_token_entity(reversed_dataframe, entity_value_no_comma)
                    if box:
                        bounding_boxes.append(box)
                        entity_names.append(entity_name)

        self.draw_bounding_boxes(image_path, bounding_boxes, entity_names)

# Example usage
if __name__ == "__main__":
    bbox_finder = BoundingBoxFinder()
    bbox_finder.process_data("/home/ritik1s/Desktop/bbox_issues/temp_GPT/row_skip.json", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.csv", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.jpeg")

# bbox_finder.main("/home/ritik1s/Desktop/bbox_issues/temp_GPT/row_skip.json", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.csv", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.jpeg")