3ba86b2ea8
Bulk import of the working lanes that were living untracked on the PC. Content: - characters/ Lena/male body lanes, bakes, texture work, run logs - clothing/ garment pipeline, configs, gates, contract docs - garments/ MD-authored garment sources (.zprj/.zpac) - UAL-Lib/ Universal Animation Library 2 source (.blend/.fbx/.glb) - tools/ blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent - docs/, plans/, dev/, .agents/plans/ Repo hygiene: - .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out on purpose — ~250 are already tracked raw and converting them would rewrite every one without shrinking history. - .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies dropped by a `>/dev/null` redirect on Windows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
3.0 KiB
Python
81 lines
3.0 KiB
Python
# Stage 34 (read-only): measure the APPROVED concept turnaround against our body, in the same
|
|
# units, so "uniform skin" stops being an adjective.
|
|
#
|
|
# blender --background --python 34_target_tone.py -- <concept_dir> <our_render.png>
|
|
#
|
|
# Both inputs are renders of a body on a flat grey background, so the comparison is like for like.
|
|
# Redness (r-g) is the useful statistic: it barely moves with lighting, so a body lit by one light
|
|
# should hold it nearly constant. Lena's Tripo texture does not — her feet measure 50% redder than
|
|
# her belly — and the question is what the approved reference does.
|
|
import bpy, sys, os
|
|
import numpy as np
|
|
|
|
argv = sys.argv[sys.argv.index("--") + 1:]
|
|
CONCEPT_DIR, OURS = argv[0], argv[1]
|
|
|
|
|
|
def load(p):
|
|
im = bpy.data.images.load(p)
|
|
w, h = im.size
|
|
b = np.empty(w * h * 4, dtype=np.float32)
|
|
im.pixels.foreach_get(b)
|
|
a = b.reshape(h, w, 4)[:, :, :3].copy()
|
|
bpy.data.images.remove(im)
|
|
return a[::-1] # Blender rows are bottom-up; flip to image order
|
|
|
|
|
|
def body_mask(A):
|
|
# background is flat and desaturated; skin is not
|
|
sat = A.max(axis=2) - A.min(axis=2)
|
|
return (sat > 0.06) & (A.max(axis=2) > 0.15)
|
|
|
|
|
|
def report(name, A):
|
|
m = body_mask(A)
|
|
if m.sum() < 500:
|
|
print(f"{name}: no body found")
|
|
return
|
|
ys, xs = np.nonzero(m)
|
|
y0, y1 = ys.min(), ys.max()
|
|
rg = (A[:, :, 0] - A[:, :, 1])
|
|
luma = A.mean(axis=2)
|
|
print(f"\n=== {name} === {m.sum()} body pixels, rows {y0}..{y1}")
|
|
print(f" WHOLE BODY r-g mean {rg[m].mean():.3f} std {rg[m].std():.3f} "
|
|
f"luma mean {luma[m].mean():.3f}")
|
|
# slice by height: 0 = top of the figure (head), 1 = bottom (feet)
|
|
print(" by height band (0=head .. 1=feet):")
|
|
rows = []
|
|
for i in range(10):
|
|
a = y0 + (y1 - y0) * i / 10.0
|
|
b = y0 + (y1 - y0) * (i + 1) / 10.0
|
|
band = np.zeros_like(m)
|
|
band[int(a):int(b) + 1, :] = True
|
|
mm = m & band
|
|
if mm.sum() < 200:
|
|
continue
|
|
rows.append((i / 10.0, rg[mm].mean(), luma[mm].mean(), int(mm.sum())))
|
|
print(f" {i/10.0:.1f}-{(i+1)/10.0:.1f} r-g {rg[mm].mean():.3f} "
|
|
f"luma {luma[mm].mean():.3f} n={int(mm.sum())}")
|
|
if rows:
|
|
arr = np.array([r[1] for r in rows])
|
|
print(f" SPREAD of r-g across height bands: {arr.max()-arr.min():.3f} "
|
|
f"(min {arr.min():.3f} at {rows[int(np.argmin(arr))][0]:.1f}, "
|
|
f"max {arr.max():.3f} at {rows[int(np.argmax(arr))][0]:.1f})")
|
|
return rg[m], luma[m]
|
|
|
|
|
|
res = {}
|
|
for f in sorted(os.listdir(CONCEPT_DIR)):
|
|
if f.lower().endswith(".png"):
|
|
res[f] = report("CONCEPT " + f, load(os.path.join(CONCEPT_DIR, f)))
|
|
ours = report("OURS " + os.path.basename(OURS), load(OURS))
|
|
|
|
print("\n" + "=" * 72)
|
|
print("VERDICT — how much does redness vary over the body?")
|
|
for k, v in res.items():
|
|
if v:
|
|
print(f" concept {k:<32} r-g std {v[0].std():.4f}")
|
|
if ours:
|
|
print(f" ours {os.path.basename(OURS):<32} r-g std {ours[0].std():.4f}")
|
|
print("TARGET_TONE_DONE")
|