File size: 2,506 Bytes
88b5236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""
General utility functions for mvtec-anomaly-benchmark.

Provides formatting, statistics, and image processing helpers.
"""

import numpy as np
from PIL import Image


# =============================================================================
# FORMATTING
# =============================================================================

def format_metric(value, decimals: int = 4) -> str:
    """
    Formats a metric value for printing.
    
    Args:
        value: Numeric value or None
        decimals: Number of decimal places
    
    Returns:
        Formatted string
    """
    if isinstance(value, float):
        return f"{value:.{decimals}f}"
    return str(value) if value is not None else "N/A"


# =============================================================================
# STATISTICS
# =============================================================================

def safe_mean(values: list) -> float | None:
    """
    Calculates mean ignoring None values.
    
    Args:
        values: List of numeric values (may contain None)
    
    Returns:
        Mean value or None if no valid values
    """
    valid = [v for v in values if v is not None]
    return sum(valid) / len(valid) if valid else None


# =============================================================================
# IMAGE PROCESSING
# =============================================================================

def resize_to_match(array: np.ndarray, target_shape: tuple) -> np.ndarray:
    """
    Resizes an array to match the target shape.
    
    Args:
        array: Input numpy array (2D, values 0-1)
        target_shape: Target (height, width) tuple
    
    Returns:
        Resized array
    """
    if array.shape == target_shape:
        return array
    
    scaled = (array * 255).astype(np.uint8)
    pil_img = Image.fromarray(scaled)
    pil_img = pil_img.resize((target_shape[1], target_shape[0]), Image.BILINEAR)
    return np.array(pil_img) / 255.0


def scale_efficientad_score(score: float) -> float:
    """
    Scales EfficientAD anomaly score to be more interpretable.
    
    Args:
        score: Raw anomaly score
    
    Returns:
        Scaled score (0-1 range, pushed towards extremes)
    """
    if score < 0.5:
        # Good: use power function to push low (e.g. 0.4998 -> ~0.25)
        return (score * 2) ** 2 / 4
    else:
        # Anomaly: steep sigmoid to push high (e.g. 0.5063 -> ~0.96)
        k = 500
        return 1 / (1 + np.exp(-k * (score - 0.5)))