I want to change my python script:
```py
import os
import shutil
from PIL import Image
from transformers import BeitImageProcessor, BeitForImageClassification
from huggingface_hub import hf_hub_download
import onnxruntime as rt
import torch
from tqdm import tqdm, trange
from concurrent.futures import ThreadPoolExecutor
import math
import numpy as np
import cv2


# Define model paths
MODEL_SALT_BESTIMAGE = "saltacc/beit-bestimage-salt"
MODEL_CAFE_AESTHETIC = "cafeai/cafe_aesthetic"

# Initialize predictors
class Predictor:
    def __init__(self, model_id: str, device = 'cpu') -> None:
        self.processor = BeitImageProcessor.from_pretrained(model_id)
        self.model = BeitForImageClassification.from_pretrained(model_id).to(device)
        self.device = device

    def predict(self, images: list[Image.Image]) -> list[dict[str, float]]:
        inputs = self.processor(images, return_tensors="pt").to(self.device)
        logits = self.model(**inputs).logits.softmax(1)
        
        results = []
        for scores in logits:
            result = {}
            for i, score in enumerate(scores):
                result[self.model.config.id2label[i]] = score.item()
            results.append(result)
        return results

# Image preprocessing for ONNX model
def preprocess_image_onnx(img, size=768):
    img = img.astype(np.float32) / 255
    h, w = img.shape[:2]
    new_h, new_w = (size, int(size * w / h)) if h > w else (int(size * h / w), size)
    ph, pw = size - new_h, size - new_w
    img_input = np.zeros((size, size, 3), dtype=np.float32)
    img_input[ph//2:ph//2+new_h, pw//2:pw//2+new_w] = cv2.resize(img, (new_w, new_h))
    img_input = np.transpose(img_input, (2, 0, 1))
    return img_input[np.newaxis, :]

# Calculate a combined score
def calculate_combined_score(score_dicts):
    # DEBUG
    # print(f"{score_dicts!r}")

    beit_score = (score_dicts['beit']['meh'] * 0.0 + 
                  score_dicts['beit']['good'] * 0.5 + 
                  score_dicts['beit']['best'] * 1.0)
    
    cafe_score = (score_dicts['cafe']['not_aesthetic'] * 0 + 
                  score_dicts['cafe']['aesthetic'] * 1.0)
    
    anime_score = score_dicts.get('anime', 0)

    # Prevent scores from being zero or negative
    epsilon = 1e-6
    beit_score = max(epsilon, beit_score)
    cafe_score = max(epsilon, cafe_score)
    anime_score = max(epsilon, anime_score)
    
    # # Basic average
    # combined_score = (beit_score + cafe_score + anime_score) / 3.0 # They are all 0.0 - 1.0, so divide to get the average

    # # Weighted average TODO: Maybe figure out the accuracy of each model to make this work
    # weight_beit = 1.0
    # weight_cafe = 0.8
    # weight_anime = 0.9

    # combined_score = (
    #     (beit_score * weight_beit) +
    #     (cafe_score * weight_cafe) +
    #     (anime_score * weight_anime)
    # ) / (weight_beit + weight_cafe + weight_anime)

    # Geometric Mean - More sensitive to lower scores
    combined_score = (beit_score * cafe_score * anime_score) ** (1/3)

    # # Harmonic Mean - Greatly emphasized low scores, more than geometrix mean.
    # combined_score = 3 / ((1/beit_score) + (1/cafe_score) + (1/anime_score))

    # # Robust Scaling and Normalization - If distribution normalization is necessary due to skewed outputs.
    # # consider scaling them based on observed distribution in training or testing
    # # Example, assuming distribution analysis led to this
    # min_beit, max_beit = 0.0, 1.0
    # min_cafe, max_cafe = 0.0, 1.0
    # min_anime, max_anime = 0.0, 1.0

    # scaled_beit_score = (beit_score - min_beit) / (max_beit - min_beit)
    # scaled_cafe_score = (cafe_score - min_cafe) / (max_cafe - min_cafe)
    # scaled_anime_score = (anime_score - min_anime) / (max_anime - min_anime)

    # combined_score = (scaled_beit_score + scaled_cafe_score + scaled_anime_score) / 3.0

    """
    Considerations
        Weight Fine-Tuning: If weights are chosen, you might want to dynamically adjust them based on preliminary results or cross-validation.
        Test and Evaluate: Start with the geometric or harmonic mean to address extreme values, and iteratively test your method against known sets of high and low aesthetic quality images to check for balance.
        Score Interdependence: If you observe that certain models tend to output more extreme scores frequently, consider reducing their influence proportionally.
        Adaptive Normalization: You can use quantile normalization or other statistical methods for better adjustment when dealing with non-uniform distributions from your models.
    Incorporate these strategies and evaluate the outputs across different subsets of your data, systematically refining the combination method to suit your image dataset and intended use case more accurately.
    """

    combined_score = combined_score.real if isinstance(combined_score, complex) else combined_score # Convert complex
    print(f"Scores - Beit: {beit_score}, Cafe: {cafe_score}, Anime: {anime_score}, Combined: {combined_score}")
    return combined_score
    

def load_image(path):
    return Image.open(path).convert('RGB')

def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

def analyze_images_in_batches(models, onnx_model, image_paths, batch_size):
    analysis_results = []

    # Calculate total number of batches
    total_batches = math.ceil(len(image_paths) / batch_size)

    with ThreadPoolExecutor() as executor:
        # Convert to PIL images
        for batch_paths in tqdm(chunker(image_paths, batch_size), total=total_batches, desc="Processing batches"):
            images = list(executor.map(load_image, batch_paths))
            # images = [Image.open(p).convert('RGB') for p in batch_paths]
            images_cv2 = [np.array(img) for img in images]  # Convert PIL to CV2 format

            # Predict with Beit and Cafe (Hugging Face transformer models)
            beit_results = models[MODEL_SALT_BESTIMAGE].predict(images)
            cafe_results = models[MODEL_CAFE_AESTHETIC].predict(images)
            
            # for i in range(len(images)):
                # img_input = preprocess_image_onnx(images_cv2[i])
                # anime_score = onnx_model.run(None, {"img": img_input})[0].item()

                # score = calculate_combined_score({
                #     'beit': beit_results[i], 
                #     'cafe': cafe_results[i], 
                #     'anime': anime_score
                # })
                # analysis_results.append((batch_paths[i], score))
            for i, img_cv2 in enumerate(images_cv2):
                img_input = preprocess_image_onnx(img_cv2)
                # img_input = torch.from_numpy(img_input).to("cuda")
                anime_score = onnx_model.run(None, {"img": img_input})[0].item()

                score = calculate_combined_score({
                    'beit': beit_results[i], 
                    'cafe': cafe_results[i], 
                    'anime': anime_score
                })
                analysis_results.append((batch_paths[i], score))
    
    return analysis_results

# Analyze all images
def analyze_images(models, onnx_model, input_folders):
    analysis_results = []
    
    for folder in input_folders:
        for file_name in tqdm(os.listdir(folder), desc=f"Processing images in {folder}"):
            file_path = os.path.join(folder, file_name)
            if os.path.isfile(file_path) and file_name.lower().endswith(('.jpeg', '.jpg', '.png', '.jfif', '.webp')):
                try:
                    image = Image.open(file_path).convert('RGB')
                    img_cv2 = np.array(image)  # Convert PIL Image to CV2 Image format

                    # Score with predictors
                    beit_results = models[MODEL_SALT_BESTIMAGE].predict([image])[0]
                    cafe_results = models[MODEL_CAFE_AESTHETIC].predict([image])[0]

                    # Model-specific preprocessing and prediction for ONNX
                    img_input = preprocess_image_onnx(img_cv2)
                    anime_score = onnx_model.run(None, {"img": img_input})[0].item()

                    # Calculate combined score
                    score = calculate_combined_score({'beit': beit_results, 'cafe': cafe_results, 'anime': anime_score})
                    analysis_results.append((file_path, score))
                except Exception as e:
                    print(f"Error processing {file_name}: {e}")
    
    return analysis_results

# Function to copy images and save scores
def copy_images_and_save_scores(images_list, destination_folder):
    for file_path, score in images_list:
        # Copy the image
        shutil.copy(file_path, destination_folder)
        # Create a score file with the same name but .txt extension
        image_name, _ = os.path.splitext(os.path.basename(file_path))
        score_file_path = os.path.join(destination_folder, f"{image_name}.txt")
        with open(score_file_path, 'w') as score_file:
            score_file.write(f"Score: {score:.4f}\n")

def main(input_folders, output_folder, top_n=100, batch_size=32):
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    device = "cpu"

    # Initialize Beit models
    models = {
        MODEL_SALT_BESTIMAGE: Predictor(MODEL_SALT_BESTIMAGE, device),
        MODEL_CAFE_AESTHETIC: Predictor(MODEL_CAFE_AESTHETIC, device),
    }

    # Load anime aesthetic model
    onnx_model_path = hf_hub_download(repo_id="skytnt/anime-aesthetic", filename="model.onnx")
    # onnx_model = rt.InferenceSession(onnx_model_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
    onnx_model = rt.InferenceSession(onnx_model_path, providers=['CPUExecutionProvider'])

    # # Collect image paths
    # image_paths = []
    # for folder in input_folders:
    #     for file_name in os.listdir(folder):
    #         if file_name.lower().endswith(('.jpeg', '.jpg', '.png', '.jfif', '.webp')):
    #             image_paths.append(os.path.join(folder, file_name))

    # Collect image paths correctly
    image_paths = []
    for folder in input_folders:
        for root, _, files in os.walk(folder):
            for file_name in files:
                if file_name.lower().endswith(('.jpeg', '.jpg', '.png', '.jfif', '.webp')):
                    image_paths.append(os.path.join(root, file_name))

    # Analyze images
    results = analyze_images_in_batches(models, onnx_model, image_paths, batch_size)

    # Sort by score and select the top N
    results.sort(key=lambda x: x[1], reverse=True)
    top_images = results[:top_n]
    bot_images = results[top_n:]

    # # Ensure output directory exists
    # os.makedirs(output_folder, exist_ok=True)

    # Create subdirectories for top and remaining images
    top_images_folder = os.path.join(output_folder, "top_images")
    remaining_images_folder = os.path.join(output_folder, "remaining_images")
    os.makedirs(top_images_folder, exist_ok=True)
    os.makedirs(remaining_images_folder, exist_ok=True)

    # # Copy top images to output folder
    # for file_path, score in top_images:
    #     shutil.copy(file_path, output_folder)
    #     print(f"Copied {os.path.basename(file_path)} with combined score {score:.4f}")

    # Copy top images and save scores
    print("Copying top images...")
    copy_images_and_save_scores(top_images, top_images_folder)

    # Copy remaining images and save scores
    print("Copying remaining images...")
    copy_images_and_save_scores(bot_images, remaining_images_folder)


def safe_main(input_folders, output_folder, initial_batch_size=32, top_n=100, min_batch_size=1, max_retries=5):
    batch_size = initial_batch_size
    retries = 0

    while batch_size >= min_batch_size and retries < max_retries:
        try:
            print(f"Trying batch size: {batch_size}")
            main(input_folders, output_folder, top_n=top_n, batch_size=batch_size)
            print("Processing completed successfully.")
            break  # Exit loop if successful
        except RuntimeError as e:
            if "out of memory" in str(e).lower():
                print(f"Out of memory error encountered with batch size: {batch_size}")
                if batch_size == min_batch_size:
                    print(f"Already at minimum batch size of {min_batch_size}. Cannot reduce further.")
                    break # Exit if already at the smallest permissible batch size
                batch_size //= 2
                retries += 1
                print(f"Retrying with reduced batch size: {batch_size}")
            else:
                raise e  # Re-raise exceptions that are not memory-related
            
    if retries == max_retries:
        print(f"Max retries reached. Unable to process with batch size as low as {batch_size}.")


if __name__ == "__main__":
    input_folders = [
        # R"G:\_AI\saved\bing",  # This will now cover all subdirectories within 'bing'
        # R"G:\_AI\saved\bing\2023",
        # R"G:\_AI\saved\bing\2024-02",
        # R"G:\_AI\saved\bing\2024-03-A",
        # R"G:\_AI\saved\bing\2024-03-B",
        # R"G:\_AI\saved\bing\2024-03-C",
        # R"G:\_AI\saved\bing\2024-04-A",
        # R"G:\_AI\saved\bing\2024-04-B",

        # R"G:\_AI\saved\Online",
        # R"G:\_AI\saved\recraft.ai",
        # R"G:\_AI\saved\unstability.ai\2024-03",
        # 'venice_v1' has multiple folders; listing higher-level directory if desired
        # R"G:\_AI\venice_v1", # Can add these later
        # R"G:\_AI\venice_v1\images_jinja2test",
        # R"G:\_AI\saved\_Meta",
        # R"G:\_AI\fal.ai\images",
        # Add other top-level directories that encompass your images

        # R"G:\_AI\training data\aesthetic_score_all",
        # R"G:\_AI\venice_v1\images_postcap",
        R"G:\_AI\training data\1013106_training_data.DALLE",
    ]  # List your input paths
    output_folder = R"G:\_AI\training data\aesthetic_score-2024-04"
    
    # main(input_folders, output_folder, top_n=1000, batch_size=16)
    safe_main(input_folders, output_folder, initial_batch_size=16)

"""
Adjusting and Monitoring
    Dynamic Adjustment:
        Use iterative testing to find the highest batch size that doesn't result in an out of memory error.
        Consider using PyTorch's torch.cuda.memory_reserved() and torch.cuda.memory_allocated() functions immediately before and after model calls to experiment with VRAM usage insights.
    Foresee Future Overhead:
        Consider system restart if needed associated with persistent memory allocation tension due to memory fragmentation, sometimes caused by CUDA's runtime environment form earlier out-of-memory interruptions.
By incrementally adjusting batch size while observing VRAM usage, you'll optimize GPU performance without exceeding resource constraints and prevent exceptions from disrupting your processing workflow.
"""
```
Right now the script gets the top N scoring images, and splits them up, but what I want now, is have the ability to instead create a score threshold, saving images above a certain threshold in the top folder, and the rest in the remaining folder.
It would be great to have some kind of option so I don't need to comment/uncomment parts of the code to swap between them.
Please just give me the parts of the script I need to change, where the change needs to be made, and what the change is.