Remaster twenty-nine original pictures with reproducible artwork tools
This commit is contained in:
1 parent
6e4008c62f
commit
668b3ff86d
253 files changed
+670875
-79
No files matched your search
@@ -0,0 +1,327 @@
|
||||
"""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)®ion 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()
|
||||
@@ -0,0 +1,261 @@
|
||||
"""Synthetic only: no actual SHIP15 image, Source or recipe is rendered."""
|
||||
from pathlib import Path
|
||||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
import struct
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
HERE=Path(__file__).resolve().parent
|
||||
spec=importlib.util.spec_from_file_location('private_ship15', HERE/'ship15_component_cleanup.py')
|
||||
tool=importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(tool)
|
||||
DECODER=HERE.parent.parent/'tools/hd_frame_policy.py'
|
||||
TONE=[(38,7,7,6),(42,7,3,1),(43,7,4,3),(46,13,31,30),(47,13,30,28),(48,13,29,27),
|
||||
(49,13,28,26),(50,13,29,27),(51,13,27,24),(42,14,27,25),(43,14,29,27),(44,14,27,25)]
|
||||
|
||||
|
||||
def binding(p):
|
||||
return dict(path=str(p),sha256=tool.sha(p.read_bytes()))
|
||||
|
||||
|
||||
def encode_source(arr):
|
||||
out=bytearray(struct.pack('<5i',0,2,18,72,24))
|
||||
for y in range(24):
|
||||
x=0
|
||||
while x<72:
|
||||
if arr[y,x]==256:
|
||||
x+=1
|
||||
continue
|
||||
end=x+1
|
||||
while end<72 and arr[y,end]<256:end+=1
|
||||
out.extend(struct.pack('<4i',x,y,y*72+x,end-x))
|
||||
out.extend(bytes(arr[y,x:end].tolist()))
|
||||
x=end
|
||||
out.extend(struct.pack('<4i',0,0,-1,0))
|
||||
return bytes(out)
|
||||
|
||||
|
||||
class SyntheticFamily(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp=tempfile.TemporaryDirectory()
|
||||
self.root=Path(self.temp.name)
|
||||
self.palette=np.zeros((256,3),np.uint8)
|
||||
for i in range(32):self.palette[i]=63-2*i
|
||||
self.palette[144:158]=[5,25,30]
|
||||
self.palette[255]=[54,54,54]
|
||||
pp=self.root/'PALETTE';pp.write_bytes(self.palette.tobytes())
|
||||
base=np.full((24,72),256,np.uint16)
|
||||
base[2:20]=144
|
||||
base[7:15,36:53]=10
|
||||
base[4:7,39:42]=10
|
||||
base[15:18,39:42]=10
|
||||
base[7:15,21:29]=10
|
||||
base[7:15,55:63]=10
|
||||
base[10:12,37]=255
|
||||
tone_set={(x,y)for x,y,_,_ in TONE}
|
||||
black=[]
|
||||
for y,x in np.argwhere(base<32):
|
||||
if (int(x),int(y))not in tone_set:
|
||||
black.append((int(x),int(y)))
|
||||
if len(black)==15:break
|
||||
for x,y in black:base[y,x]=31
|
||||
self.sources=[]
|
||||
self.raw=np.zeros((144,144,4),np.uint8)
|
||||
frames=[]
|
||||
for i in range(3):
|
||||
s=base.copy()
|
||||
for x,y,v0,v1 in TONE:s[y,x]=v0 if i==0 else v1
|
||||
self.sources.append(s)
|
||||
p=self.root/f'SOURCE{i}';p.write_bytes(encode_source(s))
|
||||
alpha=np.repeat(np.repeat(s<256,2,axis=0),2,axis=1)
|
||||
gray=np.repeat(np.repeat(s<32,2,axis=0),2,axis=1)
|
||||
yy,xx=np.indices(alpha.shape)
|
||||
phase=np.zeros((48,144,4),np.uint8)
|
||||
phase[:,:,:3][alpha]=np.stack([10+xx%5,90+yy%10,110+xx%10],axis=2)[alpha]
|
||||
neutral=(60+xx%70+yy%11+i*7).astype(np.uint8)
|
||||
for c in range(3):phase[:,:,c][gray]=neutral[gray]
|
||||
phase[:,:,3][alpha]=255
|
||||
self.raw[i*48:(i+1)*48]=phase
|
||||
comps={}
|
||||
for name,(sx,sy)in tool.AXES.items():
|
||||
comps[name]=dict(source_x=sx,raw_x=[v*2 for v in sx],source_y=sy,raw_y=[v*2 for v in sy],
|
||||
donor_bounds=[sx[0]*2,sy[0]*2,sx[-1]*2,sy[-1]*2])
|
||||
frames.append(dict(asset=f'{0x1030b+i:05x}',frame=i,source=binding(p),logical_canvas=[72,24],origin=[0,0],
|
||||
raw_crop=[0,i*48,144,48],components=comps,functional_original_cells=[]))
|
||||
phases=np.any(np.stack(self.sources)!=self.sources[0],axis=0)&(self.sources[0]<32)
|
||||
for f,s in zip(frames,self.sources):
|
||||
f['functional_original_cells']=[[int(x),int(y)]for y,x in np.argwhere((s==31)|phases|(s==255))]
|
||||
self.assertEqual(len(f['functional_original_cells']),29)
|
||||
self.parent=self.root/'parent.png'
|
||||
Image.fromarray(self.raw).save(self.parent)
|
||||
self.recipe=dict(format_version=1,label='SYNTHETIC ONLY',decoder_module=binding(DECODER),palette=binding(pp),
|
||||
parent_raw=binding(self.parent),output_scale=8,caps=copy.deepcopy(tool.CAPS),frames=frames)
|
||||
|
||||
def tearDown(self):
|
||||
self.temp.cleanup()
|
||||
|
||||
def save_parent(self):
|
||||
Image.fromarray(self.raw).save(self.parent)
|
||||
self.recipe['parent_raw']=binding(self.parent)
|
||||
|
||||
def save_source(self,i):
|
||||
p=self.root/f'SOURCE{i}';p.write_bytes(encode_source(self.sources[i]));self.recipe['frames'][i]['source']=binding(p)
|
||||
|
||||
def assert_fails_without_output(self,recipe,pattern):
|
||||
rp=self.root/'bad-recipe.json';rp.write_text(json.dumps(recipe))
|
||||
out=self.root/'MUST-NOT-EXIST'
|
||||
with self.assertRaisesRegex((ValueError,AssertionError),pattern):tool.run_recipe(rp,out)
|
||||
self.assertFalse(out.exists())
|
||||
|
||||
def test_identity_preserves_genuine_RGB_alpha_and29_Original_only(self):
|
||||
arrays,report=tool.render_family(self.recipe,self.root)
|
||||
pal=((self.palette.astype(np.uint16)<<2)|(self.palette.astype(np.uint16)>>4)).astype(np.uint8)
|
||||
for i,(s,out)in enumerate(zip(self.sources,arrays)):
|
||||
alpha=np.repeat(np.repeat(s<256,8,axis=0),8,axis=1)
|
||||
self.assertTrue(np.array_equal(out[:,:,3]==255,alpha))
|
||||
self.assertFalse(out[~alpha].any())
|
||||
yy,xx=np.indices(alpha.shape);expected=self.raw[i*48+(yy//4),xx//4,:3]
|
||||
f=np.zeros((24,72),bool)
|
||||
for x,y in self.recipe['frames'][i]['functional_original_cells']:f[y,x]=True
|
||||
fm=np.repeat(np.repeat(f,8,axis=0),8,axis=1)
|
||||
srgb=np.repeat(np.repeat(pal[np.minimum(s,255)],8,axis=0),8,axis=1)
|
||||
self.assertTrue(np.array_equal(out[:,:,:3][alpha&~fm],expected[alpha&~fm]))
|
||||
self.assertTrue(np.array_equal(out[:,:,:3][fm],srgb[fm]))
|
||||
self.assertEqual(sum(m['nearest_parent_extension_samples']for m in report['frames'][i]['component_donor_metrics']),0)
|
||||
self.assertFalse(np.array_equal(arrays[0],arrays[1]))
|
||||
|
||||
def test_nonuniform_left_pod_transform_keeps_own_genuine_donor(self):
|
||||
for i in range(3):
|
||||
ph=self.raw[i*48:(i+1)*48]
|
||||
original=ph[14:30,42:58].copy()
|
||||
ph[14:30,42:60,:3]=[10,95,115]
|
||||
ph[14:30,42:60,3]=255
|
||||
ph[14:30,44:60]=original
|
||||
m=self.recipe['frames'][i]['components']['left_pod']
|
||||
m['raw_x']=[44,60];m['donor_bounds']=[44,14,60,30]
|
||||
self.save_parent()
|
||||
arrays,_=tool.render_family(self.recipe,self.root)
|
||||
# An ordinary non-functional bottom-right pod point reads translated raw.
|
||||
x,y=28*8+2,14*8+2
|
||||
self.assertNotIn([28,14],self.recipe['frames'][0]['functional_original_cells'])
|
||||
self.assertEqual(arrays[0][y,x,:3].tolist(),self.raw[28,58,:3].tolist())
|
||||
self.assertEqual(int(arrays[0][y,x,3]),255)
|
||||
|
||||
def test_malformed_knots_and_relaxed_caps_fail_before_output(self):
|
||||
mutations=[('raw_x',[72,78,78,106]),('raw_y',[8,float('nan'),30,36]),('source_x',[36,39,42,54]),
|
||||
('raw_x',[True,78,84,106]),('donor_bounds',[73,8,106,36])]
|
||||
for key,value in mutations:
|
||||
with self.subTest(key=key,value=value):
|
||||
r=copy.deepcopy(self.recipe);r['frames'][0]['components']['main_machinery_mast_hull'][key]=value
|
||||
self.assert_fails_without_output(r,'knots|bounds')
|
||||
r=copy.deepcopy(self.recipe);r['caps']['metal_max_logical_distance']=2
|
||||
self.assert_fails_without_output(r,'caps')
|
||||
r=copy.deepcopy(self.recipe);r['caps']['metal_max_logical_distance']=True
|
||||
self.assert_fails_without_output(r,'caps')
|
||||
|
||||
def test_missing_alpha_no_donor_and_wrong_material_fail(self):
|
||||
original=self.raw.copy()
|
||||
self.raw[14:30,42:58,3]=0;self.save_parent()
|
||||
self.assert_fails_without_output(self.recipe,'no genuine')
|
||||
self.raw=original.copy();self.raw[14:30,42:58,:3]=[0,130,150];self.save_parent()
|
||||
self.assert_fails_without_output(self.recipe,'no genuine')
|
||||
self.raw=original.copy();self.raw[:,:,3]=255;self.save_parent()
|
||||
self.assert_fails_without_output(self.recipe,'transparent alpha')
|
||||
|
||||
def test_donor_fraction_and_distance_are_separate_hard_stops(self):
|
||||
original=self.raw.copy()
|
||||
self.raw[14:30,42:46,3]=0;self.save_parent()
|
||||
self.assert_fails_without_output(self.recipe,'missing fraction')
|
||||
self.raw=original.copy();self.raw[18:24,86:92,3]=0;self.save_parent()
|
||||
self.assert_fails_without_output(self.recipe,'distance exceeds')
|
||||
|
||||
def test_Original_selectors_and_Source_support_are_fail_closed(self):
|
||||
for value in [self.recipe['frames'][0]['functional_original_cells'][:-1],
|
||||
self.recipe['frames'][0]['functional_original_cells']+[self.recipe['frames'][0]['functional_original_cells'][0]]]:
|
||||
r=copy.deepcopy(self.recipe);r['frames'][0]['functional_original_cells']=value
|
||||
self.assert_fails_without_output(r,'Original cells')
|
||||
original=self.sources[0].copy()
|
||||
self.sources[0][1,20]=144;self.save_source(0)
|
||||
self.assert_fails_without_output(self.recipe,'support outside')
|
||||
self.sources[0]=original.copy();self.sources[0][10,37]=10;self.save_source(0)
|
||||
self.assert_fails_without_output(self.recipe,'indicator changed')
|
||||
self.sources[0]=original.copy();self.sources[0][5,5]=10;self.save_source(0)
|
||||
self.assert_fails_without_output(self.recipe,'footprint changed|support outside')
|
||||
self.sources[0]=original.copy();self.sources[0][5,5]=100;self.save_source(0)
|
||||
self.assert_fails_without_output(self.recipe,'class outside')
|
||||
|
||||
def test_source_tone12_family_order_and_full_crop_not_optional(self):
|
||||
r=copy.deepcopy(self.recipe);r['frames'][0]['frame']=False
|
||||
self.assert_fails_without_output(r,'order')
|
||||
r=copy.deepcopy(self.recipe);r['frames'][1]['raw_crop']=[0,49,144,47]
|
||||
self.assert_fails_without_output(r,'full strips')
|
||||
for i in (1,2):
|
||||
self.sources[i]=self.sources[0].copy();self.save_source(i)
|
||||
self.assert_fails_without_output(self.recipe,'phase12')
|
||||
|
||||
def test_hash_check_precedes_any_output_and_preserves_parent(self):
|
||||
before={p:tool.sha(p.read_bytes())for p in self.root.iterdir()if p.is_file()}
|
||||
r=copy.deepcopy(self.recipe);r['parent_raw']['sha256']='0'*64
|
||||
self.assert_fails_without_output(r,'hash mismatch')
|
||||
for p,sha in before.items():self.assertEqual(tool.sha(p.read_bytes()),sha)
|
||||
|
||||
def test_nearest_ties_are_raster_seeded_and_never_cascade(self):
|
||||
trust=np.zeros((5,5),bool);trust[0,0]=True;trust[4,4]=True
|
||||
distance,oy,ox=tool.nearest_owners(trust)
|
||||
self.assertTrue(np.all(trust[oy,ox]))
|
||||
self.assertEqual((int(oy[2,2]),int(ox[2,2])),(0,0))
|
||||
self.assertEqual(int(distance[2,2]),4)
|
||||
for y in range(5):
|
||||
for x in range(5):self.assertEqual(int(distance[y,x]),min(x+y,8-x-y))
|
||||
|
||||
def test_verified_decoder_bytes_execute_after_temporary_path_replacement(self):
|
||||
# Only a temporary decoder copy is replaced; no shared input is changed.
|
||||
decoder_copy=self.root/'copied_decoder.py'
|
||||
verified_bytes=DECODER.read_bytes()
|
||||
decoder_copy.write_bytes(verified_bytes)
|
||||
self.recipe['decoder_module']=binding(decoder_copy)
|
||||
expected,_=tool.render_family(self.recipe,self.root)
|
||||
checked_original=tool.checked
|
||||
replaced=[]
|
||||
malicious=b"raise RuntimeError('UNVERIFIED_TEMP_DECODER_MUST_NOT_EXECUTE')\n"
|
||||
|
||||
def verify_then_replace(binding_value,base):
|
||||
path,data=checked_original(binding_value,base)
|
||||
if path==decoder_copy.resolve():
|
||||
self.assertEqual(data,verified_bytes)
|
||||
self.assertEqual(tool.sha(data),tool.DECODER_SHA)
|
||||
path.write_bytes(malicious)
|
||||
replaced.append(path)
|
||||
return path,data
|
||||
|
||||
with patch.object(tool,'checked',side_effect=verify_then_replace):
|
||||
actual,report=tool.render_family(self.recipe,self.root)
|
||||
self.assertEqual(replaced,[decoder_copy.resolve()])
|
||||
self.assertEqual(decoder_copy.read_bytes(),malicious)
|
||||
self.assertEqual(report['decoder_module']['sha256'],tool.DECODER_SHA)
|
||||
for before,after in zip(expected,actual):
|
||||
self.assertTrue(np.array_equal(before,after))
|
||||
self.assertFalse(any(p.name.endswith('-derived.png')for p in self.root.iterdir()))
|
||||
|
||||
def test_deterministic_recipe_receipts_and_new_output_only(self):
|
||||
rp=self.root/'recipe.json';rp.write_text(json.dumps(self.recipe,sort_keys=True))
|
||||
before={p:tool.sha(p.read_bytes())for p in self.root.iterdir()if p.is_file()}
|
||||
first=tool.run_recipe(rp,self.root/'first')
|
||||
second=tool.run_recipe(rp,self.root/'second')
|
||||
self.assertEqual(first,second)
|
||||
self.assertEqual(set(first['execution']),{'status','method','recipe_sha256','tool_sha256','source_sha256','parent_raw_sha256','output_sha256'})
|
||||
self.assertEqual(first['decoder_module']['sha256'],tool.DECODER_SHA)
|
||||
self.assertIn('numpy_version',first)
|
||||
for name in ['1030b-derived.png','1030c-derived.png','1030d-derived.png','derived-provenance.json']:
|
||||
self.assertEqual((self.root/'first'/name).read_bytes(),(self.root/'second'/name).read_bytes())
|
||||
with self.assertRaisesRegex(ValueError,'output exists'):tool.run_recipe(rp,self.root/'first')
|
||||
for p,sha in before.items():self.assertEqual(tool.sha(p.read_bytes()),sha)
|
||||
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in new issue
Block a user