- Precompute edit_vec = (strength * sign) * dc_removed_pc1_dir once in
closure instead of recomputing pc1_dir mean removal every denoising step
- Remove SharpnessData.pc1_std (stored but never used since dropping
pc1_std multiplier)
- Blur single-channel gray noise, expand to 3-channel per-batch instead
of allocating full 201 MB 3-channel copy upfront
- Remove unused LCSData import
256 vs 512 difference (97.5% vs 97.3%) is within random variation —
different image_size consumes different random numbers from the same
seed, producing different images rather than a controlled comparison.
Keep 512 consistent with LCS color calibration. Remove per-blur-level
mean logging that was used during development.
256px gives the best PC1 (97.5%) and fastest calibration.
Larger images average more patches, reducing per-sample variance
but not improving PCA purity. Added image_size input to
LCSSharpnessCalibrate for experimentation.
RGB noise has independent per-channel variation that blur affects
differently, introducing inter-channel variance captured as PC2.
Grayscale noise (same value across RGB) ensures blur affects all
channels identically, raising PC1 from 95.8% to 96.7%.
Also added per-vector zero-mean to remove VAE brightness drift
and diagnostic logging for per-blur-level latent means.
When calibration used LCS color removal, the intervention must also
remove the color component before projecting onto the sharpness basis,
then add it back after reconstruction. This ensures sharpness edits
don't affect color.
Workflow:
1. LCSSharpnessCalibrate(vae, lcs_data) → sharpness_data
2. LCSSharpnessIntervene(model, sharpness_data, strength, lcs_data=lcs_data)
Both nodes now have optional lcs_data input. When connected, color is
preserved during sharpness intervention.
When LCS color data is connected, the sharpness PC1 will be orthogonal
to the color subspace, preventing color shifts during intervention.
Changes:
- calibrate_sharpness() now accepts optional lcs_data parameter
- LCSSharpnessCalibrate node has optional lcs_data input
- Cache files use "_lcs" suffix when LCS is used (different subspace)
- Generate base images once upfront, then apply all blur levels to the
SAME images (was generating different random images per blur level)
- Add separable convolution for kernel_size > 15 (O(2k) vs O(k²))
- Add kernel caching to avoid recomputation
- Add per-blur-level progress logging
This matches SubspaceLab's methodology and ensures PCA receives
properly paired stimuli (same image × multiple blur levels).
New calibration (core/sharpness.py) generates blur stimuli, VAE-encodes,
and extracts PC1 as the sharpness direction. New nodes (nodes/sharpen.py)
provide LCSSharpnessCalibrate (auto-cached per-VAE) and
LCSSharpnessIntervene (post-CFG hook with strength, step window, mask).
Positive strength = sharper, negative = blurrier.