File size: 3,027 Bytes
e9190ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Version 2 editing protocol: independent noise and inspectable RGBA diagnostics.

Diagnostics are descriptive, not quality scores or ground-truth matting metrics.
No filtering, sharpening, alpha thresholding or cleanup is applied to outputs.
"""
import math
from pathlib import Path

import numpy as np
from PIL import Image, ImageOps

PROTOCOL = 'editing-v2'
DEFAULT_SEEDS = (1000042, 1000123)


def validate_seeds(cases, seeds):
    if (not seeds or len(set(seeds)) != len(seeds)
            or any(type(s) is not int or not 0 <= s < 2**63 for s in seeds)):
        raise ValueError('Use distinct integer seeds in [0, 2**63)')
    source_seeds = {c.get('source_seed') for c in cases} - {None}
    if source_seeds.intersection(seeds):
        raise ValueError('Editing seeds must differ from every known source seed (noise replay)')
    return list(seeds)


def load_input(path):
    with Image.open(path) as image:
        image = ImageOps.exif_transpose(image)
        has_alpha = 'A' in image.getbands() or 'transparency' in image.info
        return image.convert('RGBA' if has_alpha else 'RGB')


def edit_dimensions(size, resolution):
    if resolution < 32 or resolution % 32 or min(size) <= 0:
        raise ValueError('Positive image size and resolution divisible by 32 required')
    ratio = size[0] / size[1]
    width = math.sqrt(resolution**2 * ratio)
    return max(32, round(width / 32)*32), max(32, round(width / ratio / 32)*32)


def image_diagnostics(image):
    rgba = np.asarray(image.convert('RGBA'))
    alpha = rgba[:, :, 3]
    # The thresholds are explicitly recorded; a white RGB background is not transparent.
    rgb = rgba[:, :, :3].astype(np.float32) / 255
    return dict(image_mode=image.mode, actual_size=list(image.size),
                alpha_min=int(alpha.min()), alpha_max=int(alpha.max()),
                alpha_transparent_fraction=float((alpha <= 5).mean()),
                alpha_opaque_fraction=float((alpha >= 250).mean()),
                alpha_soft_fraction=float(((alpha > 5) & (alpha < 250)).mean()),
                alpha_thresholds=[5, 250],
                rgb_dx_mean=float(np.abs(np.diff(rgb, axis=1)).mean()) if image.width > 1 else 0.,
                rgb_dy_mean=float(np.abs(np.diff(rgb, axis=0)).mean()) if image.height > 1 else 0.)


def save_diagnostics(image, output, stem):
    output = Path(output)
    output.mkdir(parents=True, exist_ok=True)
    rgba = image.convert('RGBA')
    rgba.getchannel('A').save(output/f'{stem}-alpha.png')
    for name, color in [('white', 'white'), ('black', 'black')]:
        background = Image.new('RGBA', image.size, color)
        Image.alpha_composite(background, rgba).convert('RGB').save(output/f'{stem}-{name}.png')
    yy, xx = np.indices((image.height, image.width))
    grid = np.where((xx//16 + yy//16) % 2, 192, 240).astype(np.uint8)
    background = Image.fromarray(np.repeat(grid[:, :, None], 3, axis=2)).convert('RGBA')
    Image.alpha_composite(background, rgba).convert('RGB').save(output/f'{stem}-checker.png')