Files
RustRaptor/tools/ship15_component_registration/ship15_component_cleanup.py

328 lines
17 KiB
Python

"""Private SHIP15G1 complete3 component registration; not artistic approval.
Genuine generated RGB is sampled only from the same phase/material. Literal
Source support supplies derived alpha; exactly29 declared logical cells supply
baked Original RGB. Parent PNG/Source files are never changed. This file is
prepared code only until Root releases an actual recipe execution.
"""
from __future__ import annotations
import argparse
from collections import deque
import hashlib
import io
import json
import math
from pathlib import Path
import platform
import numpy as np
from PIL import Image, __version__ as PILLOW_VERSION
METHOD = 'derived-generated-ship15-component-mast-pods-wake-source-support-v1'
DECODER_SHA = '66184a4758027dadbadb5e20fe90b2add2b9fc218b7810304b1a1ea2de74f58e'
CAPS = dict(metal_max_logical_distance=1.0, metal_max_missing_fraction=0.13,
wake_max_logical_distance=2.75, wake_max_missing_fraction=0.18)
AXES = {
'main_machinery_mast_hull': ([36, 39, 42, 53], [4, 7, 15, 18]),
'left_pod': ([21, 29], [7, 15]),
'right_pod': ([55, 63], [7, 15]),
'teal_wake': ([0, 21, 29, 36, 53, 55, 63, 72], [2, 4, 7, 15, 18, 20]),
}
def sha(data):
return hashlib.sha256(data).hexdigest()
def exact_keys(value, keys, label):
if type(value) is not dict or set(value) != set(keys):
raise ValueError(f'{label}: unknown/missing fields')
def checked(binding, base):
exact_keys(binding, {'path', 'sha256'}, 'input binding')
if type(binding['path']) is not str or not binding['path'] or type(binding['sha256']) is not str:
raise ValueError('input path/hash types')
path = Path(binding['path'])
if not path.is_absolute():
path = Path(base) / path
data = path.read_bytes()
if sha(data) != binding['sha256']:
raise ValueError(f'input hash mismatch: {path}')
return path.resolve(), data
def finite_number(v):
return type(v) in (int, float) and math.isfinite(v)
def validate_model(model, name, width, height):
exact_keys(model, {'source_x', 'raw_x', 'source_y', 'raw_y', 'donor_bounds'}, name)
expected_x, expected_y = AXES[name]
for key, expected in [('source_x', expected_x), ('source_y', expected_y)]:
if type(model[key]) is not list or model[key] != expected or any(type(v) is not int for v in model[key]):
raise ValueError(f'{name}: Source knots differ')
for source_key, raw_key, maximum in [('source_x', 'raw_x', width), ('source_y', 'raw_y', height)]:
values = model[raw_key]
if (type(values) is not list or len(values) != len(model[source_key]) or
any(not finite_number(v) for v in values) or
not all(0 <= v <= maximum for v in values) or
not all(b > a for a, b in zip(values, values[1:]))):
raise ValueError(f'{name}: nonfinite/nonmonotone/outside raw knots')
bounds = model['donor_bounds']
if (type(bounds) is not list or len(bounds) != 4 or any(type(v) is not int for v in bounds) or
not (0 <= bounds[0] < bounds[2] <= width and 0 <= bounds[1] < bounds[3] <= height)):
raise ValueError(f'{name}: invalid donor bounds')
if not (bounds[0] <= model['raw_x'][0] <= model['raw_x'][-1] <= bounds[2] and
bounds[1] <= model['raw_y'][0] <= model['raw_y'][-1] <= bounds[3]):
raise ValueError(f'{name}: knots escape same-component donor bounds')
def nearest_owners(trusted):
"""Raster seeds, N/W/E/S BFS; owners always refer to initial trusted pixels."""
if not trusted.any():
raise ValueError('no genuine parent donors for component/material')
h, w = trusted.shape
distance = np.full((h, w), np.iinfo(np.int32).max, np.int32)
owner_y = np.full((h, w), -1, np.int32)
owner_x = np.full((h, w), -1, np.int32)
seeds = np.argwhere(trusted)
todo = deque()
for y, x in seeds:
y, x = int(y), int(x)
distance[y, x] = 0
owner_y[y, x], owner_x[y, x] = y, x
todo.append((x, y))
while todo:
x, y = todo.popleft()
n = int(distance[y, x]) + 1
for nx, ny in ((x, y-1), (x-1, y), (x+1, y), (x, y+1)):
if 0 <= nx < w and 0 <= ny < h and distance[ny, nx] > n:
distance[ny, nx] = n
owner_y[ny, nx], owner_x[ny, nx] = owner_y[y, x], owner_x[y, x]
todo.append((nx, ny))
return distance, owner_y, owner_x
def sample_component(parent, source_mask, model, name, scale, caps):
"""Nearest genuine raw RGB at declared inverse-map coordinates, no gains."""
h, w = source_mask.shape
output_mask = np.repeat(np.repeat(source_mask, scale, axis=0), scale, axis=1)
yy, xx = np.nonzero(output_mask)
if len(xx) == 0:
raise ValueError(f'{name}: empty Source component')
u = np.interp((xx+0.5)/scale, model['source_x'], model['raw_x'])
v = np.interp((yy+0.5)/scale, model['source_y'], model['raw_y'])
px, py = np.floor(u).astype(int), np.floor(v).astype(int)
l, t, r, b = model['donor_bounds']
if np.any(px < l) or np.any(px >= r) or np.any(py < t) or np.any(py >= b):
raise ValueError(f'{name}: mapped sample outside donor bounds')
# Donor tests are performed on an untouched crop of this exact phase only.
patch = parent[t:b, l:r]
rgb = patch[:, :, :3].astype(np.int16)
trusted = patch[:, :, 3] >= 128
if name == 'teal_wake':
trusted &= (rgb[:, :, 1] > rgb[:, :, 0]+10) & (rgb[:, :, 2] > rgb[:, :, 0]+10)
kind = 'wake'
else:
trusted &= rgb.max(axis=2)-rgb.min(axis=2) <= 18
kind = 'metal'
distance, oy, ox = nearest_owners(trusted)
qx, qy = px-l, py-t
d = distance[qy, qx].astype(float)
direct = trusted[qy, qx]
minimum_raw_per_logical = min(
min(np.diff(model['raw_x'])/np.diff(model['source_x'])),
min(np.diff(model['raw_y'])/np.diff(model['source_y'])))
logical_distance = d/minimum_raw_per_logical
missing_fraction = float((~direct).mean())
if missing_fraction > caps[f'{kind}_max_missing_fraction'] + 1e-12:
raise ValueError(f'{name}: genuine donor missing fraction exceeds cap')
if float(logical_distance.max()) > caps[f'{kind}_max_logical_distance'] + 1e-12:
raise ValueError(f'{name}: genuine donor distance exceeds cap')
sy, sx = oy[qy, qx], ox[qy, qx]
if not np.all(trusted[sy, sx]):
raise AssertionError('owner is not an original genuine donor')
# No interpolated/new pixel can become a donor; all reads use the parent.
values = patch[sy, sx, :3].copy()
return output_mask, values, dict(component=name, Source_cells=int(source_mask.sum()),
output_samples=len(xx), direct_genuine_samples=int(direct.sum()),
nearest_parent_extension_samples=int((~direct).sum()),
missing_fraction=missing_fraction,
max_raw_Manhattan_distance=float(d.max()),
max_equivalent_logical_distance=float(logical_distance.max()),
same_phase_material_and_bounds=True, cascading_fill=False)
def validate_recipe(recipe):
exact_keys(recipe, {'format_version', 'label', 'decoder_module', 'palette', 'parent_raw',
'output_scale', 'caps', 'frames'}, 'recipe')
if type(recipe['format_version']) is not int or recipe['format_version'] != 1:
raise ValueError('recipe format version')
if type(recipe['label']) is not str or not recipe['label'].strip():
raise ValueError('recipe label')
if type(recipe['output_scale']) is not int or recipe['output_scale'] != 8:
raise ValueError('this complete3 recipe requires declared8x master')
exact_keys(recipe['caps'], CAPS, 'caps')
if any(not finite_number(recipe['caps'][k]) or recipe['caps'][k] != v for k, v in CAPS.items()):
raise ValueError('caps must equal reviewed proposed hard limits')
frames = recipe['frames']
if type(frames) is not list or len(frames) != 3:
raise ValueError('complete3 required')
for i, frame in enumerate(frames):
exact_keys(frame, {'asset', 'frame', 'source', 'logical_canvas', 'origin', 'raw_crop',
'components', 'functional_original_cells'}, 'frame')
if (frame['asset'] != f'{0x1030b+i:05x}' or type(frame['frame']) is not int or frame['frame'] != i or
frame['logical_canvas'] != [72, 24] or any(type(v) is not int for v in frame['logical_canvas']) or
frame['origin'] != [0, 0] or any(type(v) is not int for v in frame['origin'])):
raise ValueError('Source family/order/native/origin changed')
exact_keys(frame['components'], AXES, 'components')
return frames
def render_family(recipe, base):
frames = validate_recipe(recipe)
decoder_path, decoder_bytes = checked(recipe['decoder_module'], base)
if sha(decoder_bytes) != DECODER_SHA:
raise ValueError('unreviewed decoder module')
decoder_namespace = {'__name__': '_verified_ship15_decoder', '__file__': str(decoder_path)}
exec(compile(decoder_bytes, str(decoder_path), 'exec'), decoder_namespace)
decode = decoder_namespace['_indices']
palette_path, palette_bytes = checked(recipe['palette'], base)
if len(palette_bytes) != 768 or max(palette_bytes) > 63:
raise ValueError('literal256 six-bit palette required')
palette = np.frombuffer(palette_bytes, np.uint8).reshape(256, 3).astype(np.uint16)
palette = ((palette << 2) | (palette >> 4)).astype(np.uint8)
parent_path, parent_bytes = checked(recipe['parent_raw'], base)
if len(parent_bytes) > 32*1024*1024:
raise ValueError('parent exceeds32MiB')
with Image.open(io.BytesIO(parent_bytes)) as opened:
if (opened.format != 'PNG' or opened.width != opened.height or opened.height % 3 or
opened.width*opened.height > 4_194_304 or opened.width < 72):
raise ValueError('parent must be a bounded square three-full-strip PNG')
parent = np.array(opened.convert('RGBA'))
if parent[:, :, 3].min() != 0:
raise ValueError('parent must have genuine transparent alpha')
height, width = parent.shape[:2]
strip_height = height//3
sources, input_hashes = [], {str(parent_path): sha(parent_bytes), str(palette_path): sha(palette_bytes)}
for i, frame in enumerate(frames):
crop = frame['raw_crop']
if (type(crop) is not list or crop != [0, i*strip_height, width, strip_height] or
any(type(v) is not int for v in crop)):
raise ValueError('only three literal full strips permitted, no crop fit')
path, payload = checked(frame['source'], base)
sw, sh, indices = decode(payload)
if [sw, sh] != [72, 24]:
raise ValueError('Original canvas differs')
source = np.asarray(indices, np.uint16).reshape(sh, sw)
if not np.all((source < 32) | ((source >= 144) & (source <= 157)) | (source == 255) | (source == 256)):
raise ValueError('Source class outside exact declared family partition')
if np.any((source[:2] < 256)) or np.any((source[20:] < 256)):
raise ValueError('Source painted support outside declared full wake region')
if np.argwhere(source == 255).tolist() != [[10, 37], [11, 37]]:
raise ValueError('Source255 indicator changed')
for name, model in frame['components'].items():
validate_model(model, name, width, strip_height)
sources.append(source)
input_hashes[str(path)] = sha(payload)
gray = sources[0] < 32
if any(not np.array_equal(s < 32, gray) for s in sources[1:]):
raise ValueError('stationary Source gray footprint changed')
phase_cells = np.any(np.stack(sources) != sources[0], axis=0) & gray
if int(phase_cells.sum()) != 12:
raise ValueError('Source phase12 tone coordinates changed')
arrays, reports = [], []
yy, xx = np.indices((24, 72))
gray_regions = dict(main_machinery_mast_hull=(xx >= 36)&(xx < 53)&(yy >= 4)&(yy < 18),
left_pod=(xx >= 21)&(xx < 29)&(yy >= 7)&(yy < 15),
right_pod=(xx >= 55)&(xx < 63)&(yy >= 7)&(yy < 15))
gray_region_union = np.logical_or.reduce(list(gray_regions.values()))
if np.any(gray & ~gray_region_union):
raise ValueError('gray Source support outside three declared components')
for i, (frame, source) in enumerate(zip(frames, sources)):
occupied = source < 256
functional = (source == 31) | phase_cells | (source == 255)
expected = [[int(x), int(y)] for y, x in np.argwhere(functional)]
cells = frame['functional_original_cells']
if (type(cells) is not list or cells != expected or len(cells) != 29 or
any(type(cell) is not list or len(cell) != 2 or any(type(v) is not int for v in cell) for cell in cells)):
raise ValueError('exact row-major29 explicit Original cells required')
masks = {name: (source < 32)&region for name, region in gray_regions.items()}
masks['teal_wake'] = (source >= 144)&(source <= 157)
assigned = np.logical_or.reduce(list(masks.values())) | (source == 255)
if not np.array_equal(assigned, occupied):
raise ValueError('Source support partition mismatch')
rgba = np.zeros((192, 576, 4), np.uint8)
parent_phase = parent[i*strip_height:(i+1)*strip_height]
counts = []
for name, mask in masks.items():
outmask, values, record = sample_component(parent_phase, mask, frame['components'][name], name, 8, recipe['caps'])
rgba[:, :, :3][outmask] = values
counts.append(record)
alpha = np.repeat(np.repeat(occupied, 8, axis=0), 8, axis=1)
rgba[:, :, 3][alpha] = 255
before_functional = rgba.copy()
functional8 = np.repeat(np.repeat(functional, 8, axis=0), 8, axis=1)
source_rgb8 = np.repeat(np.repeat(palette[np.minimum(source, 255)], 8, axis=0), 8, axis=1)
rgba[:, :, :3][functional8] = source_rgb8[functional8]
if (not np.array_equal(rgba[:, :, 3] == 255, alpha) or np.any(rgba[~alpha]) or
not np.array_equal(rgba[:, :, :3][functional8], source_rgb8[functional8]) or
not np.array_equal(rgba[~functional8], before_functional[~functional8])):
raise AssertionError('Source alpha/functional/outside-original support invariant failed')
arrays.append(rgba)
reports.append(dict(asset=frame['asset'], frame=i, logical_canvas=[72, 24], origin=[0, 0],
component_donor_metrics=counts, Source_occupied_cells=int(occupied.sum()),
baked_Original_cells=cells, baked_Original_RGBA_pixels=29*64,
generated_detail_not_claimed_for_baked_Original=True,
alpha_contract='Literal SourceNN8 occupied support, not OriginalScaleNx alpha',
Source_alpha_and_outside_support_exact=True,
functional_Original_RGB_exact=True, outside_29_functional_RGBA_unchanged=True))
report = dict(method=METHOD, input_hashes=input_hashes, frames=reports,
decoder_module=dict(path=str(decoder_path), sha256=sha(decoder_bytes)),
python_version=platform.python_version(), python_implementation=platform.python_implementation(),
numpy_version=np.__version__, pillow_version=PILLOW_VERSION,
scope='Derived geometry/RGB registration only; no native QA or artistic approval',
generated_RGB_never_replaced_by_SourceRGB_outside_declared29=True,
parent_and_Source_unchanged=True, output_scale=8)
return arrays, report
def run_recipe(recipe_path, output):
recipe_path, output = Path(recipe_path), Path(output)
if output.exists() or output.is_symlink():
raise ValueError('output exists; never overwrite prior artwork')
data = recipe_path.read_bytes()
arrays, report = render_family(json.loads(data), recipe_path.resolve().parent)
files = []
for pixels, frame in zip(arrays, report['frames']):
buffer = io.BytesIO()
Image.fromarray(pixels).save(buffer, format='PNG', compress_level=9)
payload = buffer.getvalue()
name = frame['asset']+'-derived.png'
frame['output'] = dict(file=name, sha256=sha(payload), bytes=len(payload), RGBA_sha256=sha(pixels.tobytes()))
files.append((name, payload))
report['execution'] = dict(status='success', method=METHOD, recipe_sha256=sha(data),
tool_sha256=sha(Path(__file__).read_bytes()),
source_sha256={f['asset']: f['source']['sha256'] for f in json.loads(data)['frames']},
parent_raw_sha256=sorted({json.loads(data)['parent_raw']['sha256']}),
output_sha256={f['asset']: f['output']['sha256'] for f in report['frames']})
output.mkdir(parents=True, exist_ok=False)
for name, payload in files:
(output/name).write_bytes(payload)
(output/'derived-provenance.json').write_text(json.dumps(report, indent=2, sort_keys=True)+'\n')
return report
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('recipe', type=Path)
parser.add_argument('output_new_directory', type=Path)
args = parser.parse_args()
report = run_recipe(args.recipe, args.output_new_directory)
print(json.dumps(dict(execution=report['execution'])))
if __name__ == '__main__':
main()