SYSTEM: You are a Python programmer tasked with refactoring code. You are proficient in Python 3.12 and use modern features where appropriate. Your code is clear, easy to read, and free of unnecessary verbosity. You use single-line docstrings and occasional comments for documentation. Often, the code you receive is just one part of a larger program. USER: # --- Image Processing Functions ---

def process_grayscale_image(img: np.ndarray, path: Path, image_processor: 'ImageProcessor', temp_folder: Path) -> Tuple[Path, int, int]:
    """Process a grayscale image, adjusting its values and saving the result."""
    try:
        adjusted, modal_black, modal_white = adjust_values(img)

        out_path = temp_folder / path.parent.relative_to(Config.INPUT_FOLDER) / (path.stem + ".png")
        out_path.parent.mkdir(parents=True, exist_ok=True)
        Image.fromarray(adjusted).convert('L').save(out_path, format='PNG', compress_level=5)

        subfolder = str(path.parent.relative_to(Config.INPUT_FOLDER))
        filename = path.name
        if modal_black:
            image_processor.black_results[subfolder].append((filename, modal_black))
        if modal_white != 255:
            image_processor.white_results[subfolder].append((filename, modal_white))

        return out_path, modal_black, modal_white
    except (ValueError, OSError) as e:
        log_error(f"Error processing grayscale image {path}: {e}")
        return path, 0, 255

def process_color_image(img: np.ndarray, path: Path, image_processor: 'ImageProcessor', temp_folder: Path,
                       diff_threshold: int = Config.COLORDIFF_THRESHOLD,
                       fraction_threshold: float = Config.FRACTION_COLORED_PIXELS_THRESHOLD) -> Tuple[Path, bool]:
    """Process a color image, determining if it's color or grayscale based on RGB differences."""
    try:
        if is_color_image(img, diff_threshold, fraction_threshold):
            # It's a color image - save it as is
            rel_path = path.relative_to(Config.INPUT_FOLDER)
            out_path = temp_folder / rel_path.with_suffix('.png')
            out_path.parent.mkdir(parents=True, exist_ok=True)
            Image.fromarray(img).save(out_path, format='PNG', compress_level=5)

            subfolder = str(path.parent.relative_to(Config.INPUT_FOLDER))
            filename = path.name
            image_processor.color_results[subfolder].append(filename)

            return out_path, False
        else:
            # It's a grayscale image - process accordingly
            gray = ImageOps.grayscale(Image.fromarray(img))
            out_path, modal_black, modal_white = process_grayscale_image(np.array(gray), path, image_processor, temp_folder)
            return out_path, modal_black != 0
    except (OSError, ValueError) as e:
        log_error(f"Error processing color image {path}: {e}")
        return path, False

def adjust_values(img: np.ndarray) -> Tuple[np.ndarray, int, int]:
    """Adjust the values of a grayscale image."""
    try:
        hist, bins = np.histogram(img[img >= 250], bins=6, range=(250, 256))
        modal_white = int(bins[np.argmax(hist)])

        hist, bins = np.histogram(img[img < 61], bins=61, range=(0, 61))
        modal_black = int(bins[np.argmax(hist)])

        if modal_white != 255:
            img = img.astype(np.float64)
            img = img * (255.0 / modal_white)
            img = np.clip(img, 0, 255)
            img = img.astype(np.uint8)

        if modal_black:
            img = img.astype(np.float64)
            adjusted = (img - modal_black) * (255.0 / (255 - modal_black))
            adjusted = np.clip(adjusted, 0, 255)
            
            gamma = 1.0 - (modal_black / 255.0)
            adjusted = np.power(adjusted / 255.0, gamma) * 255 

            adjusted[img == 0] = 0
            adjusted[img == 255] = 255

            adjusted = adjusted.astype(np.uint8)
        else:
            adjusted = img

        return adjusted, modal_black, modal_white
    except ValueError as e:
        log_error(f"Error adjusting values for image: {e}")
        return img, 0, 255

def analyze_images(image_processor: 'ImageProcessor'):
    """Analyze images in the input folder for color properties."""
    image_processor.reset_results()
    tasks = [path for path in Config.INPUT_FOLDER.rglob('*') 
             if path.is_file() and path.suffix.lower() in Config.IMAGE_EXTENSIONS]
    
    if not tasks:
        log_error(f"No image files found in '{Config.INPUT_FOLDER}'")
        return

    with create_temp_folder() as temp_folder:
        run_with_thread_pool(tasks, analyze_file, time.time(), len(tasks))

def analyze_file(path, start_time, total):
    """Analyze a single image file using RGB differences to determine color status."""
    if is_valid_image(path):
        try:
            img = np.array(open_and_convert_image(path))
            if img.ndim == 2 or (img.ndim == 3 and img.shape[2] == 1):
                # Grayscale image
                adjusted, modal_black, modal_white = adjust_values(img)  
                subfolder = str(path.parent.relative_to(Config.INPUT_FOLDER))
                filename = path.name
                if modal_black:
                    image_processor.black_results[subfolder].append((filename, modal_black))
                if modal_white != 255:
                    image_processor.white_results[subfolder].append((filename, modal_white))
            else:
                # Determine if the image is color or grayscale based on RGB differences
                if is_color_image(img, Config.COLORDIFF_THRESHOLD, Config.FRACTION_COLORED_PIXELS_THRESHOLD):
                    subfolder = str(path.parent.relative_to(Config.INPUT_FOLDER))
                    filename = path.name
                    image_processor.color_results[subfolder].append(filename)
                else:
                    adjusted, modal_black, modal_white = adjust_values(img) 
                    subfolder = str(path.parent.relative_to(Config.INPUT_FOLDER))
                    filename = path.name
                    if modal_black:
                        image_processor.black_results[subfolder].append((filename, modal_black))
                    if modal_white != 255:
                        image_processor.white_results[subfolder].append((filename, modal_white))
        except (OSError, ValueError) as e:
            log_error(f"Error processing image {path}: {e}")
    with shared_state.processed_files_lock:
        shared_state.processed_files_count += 1
    update_status(start_time, shared_state.processed_files_count, total)

def find_color_images(image_processor: 'ImageProcessor'):
    """Find and move color images to the output folder."""
    image_processor.reset_results()
    tasks = [path for path in Config.INPUT_FOLDER.rglob('*') 
             if path.is_file() and path.suffix.lower() in Config.IMAGE_EXTENSIONS]
    
    if not tasks:
        log_error(f"No image files found in '{Config.INPUT_FOLDER}'")
        return

    with create_temp_folder() as temp_folder:
        run_with_thread_pool(tasks, move_color_image_file, time.time(), len(tasks), image_processor)

def move_color_image_file(path, start_time, total_files, image_processor: 'ImageProcessor'):
    """Move a color image file to the output folder."""
    if is_valid_image(path):
        try:
            img = np.array(open_and_convert_image(path))
            if img.ndim == 3 and img.shape[2] == 3:
                if is_color_image(img, Config.COLORDIFF_THRESHOLD, Config.FRACTION_COLORED_PIXELS_THRESHOLD):
                    rel_path = path.relative_to(Config.INPUT_FOLDER)
                    out_path = Config.OUTPUT_FOLDER / rel_path
                    out_path.parent.mkdir(parents=True, exist_ok=True)
                    shutil.move(str(path), str(out_path))
                    subfolder = str(path.parent.relative_to(Config.INPUT_FOLDER))
                    filename = path.name
                    image_processor.color_results[subfolder].append(filename)
        except (OSError, ValueError) as e:
            log_error(f"Error processing image {path}: {e}")

    with shared_state.processed_files_lock:
        shared_state.processed_files_count += 1
    update_status(start_time, shared_state.processed_files_count, total_files)