Turning "rank 20" into a defensible decision
Nobody can justify a rank; anybody can justify “the smallest rank holding 25 dB”.
Eckart–Young gives the squared error as sum(S[k:] ** 2) directly, so you can find the rank that reaches a target PSNR without rebuilding the truncation for each k.
On the real 512×512 astronaut image, rank 16 reaches 21.1 dB. The rank needed grows faster than linearly in dB, because the singular values decay quickly and then flatten — the last few dB cost more rank than the first twenty did.
And note what rank-16 storage actually is: 6.3% of the pixel count, but 12.5% of the bytes at int16 and 25% at float32. Truncated SVD is a superb analysis tool and a mediocre image codec.