fix(webpage): downscale large full-size ptycho PNGs before upload
CI for csaxs_bec / test (push) Failing after 2m19s
CI for csaxs_bec / test (push) Failing after 2m19s
omny.psi.ch/upload.php rejected the full-size probe/spectrum PNGs of stitched reconstructions with HTTP 413. Full-size copies above 900 kB are now saved as downscaled PNGs until they fit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ALDnH16dC6iyHjhe5Ey4w9
This commit is contained in:
1 parent
de14963f3e
commit
e5c5d64307
2 files changed
+57
-1
No files matched your search
@@ -136,6 +136,10 @@ _MISMATCH_LOCAL_PORT = 8081
|
||||
# Subfolders follow the pattern dset_NNNNN and contain S#####_*_phase.png etc.
|
||||
_PTYCHO_ONLINE_DIR = "~/data/raw/analysis/online/ptycho"
|
||||
_PTYCHO_THUMB_PX = 200 # longest edge of generated thumbnail in pixels
|
||||
# The upload server (omny.psi.ch/upload.php) rejects large POSTs with HTTP 413;
|
||||
# stitched reconstructions produce full-size PNGs above that. Full-size copies
|
||||
# larger than this are downscaled until they fit.
|
||||
_PTYCHO_FULL_MAX_BYTES = 900_000
|
||||
|
||||
VERBOSITY_SILENT = 0
|
||||
VERBOSITY_NORMAL = 1
|
||||
@@ -1324,7 +1328,7 @@ class WebpageGeneratorBase:
|
||||
img.save(thumb_path, "JPEG", quality=85)
|
||||
else:
|
||||
shutil.copy2(src, thumb_path)
|
||||
shutil.copy2(src, full_path)
|
||||
self._copy_full_capped(src, full_path)
|
||||
images.append({
|
||||
"role": role,
|
||||
"thumb": thumb_name,
|
||||
@@ -1339,6 +1343,26 @@ class WebpageGeneratorBase:
|
||||
self._last_ptycho_scan = scan_id
|
||||
return {"scan_id": scan_id, "images": images, "folder": str(latest_dir)}
|
||||
|
||||
@staticmethod
|
||||
def _copy_full_capped(src: Path, dst: Path, max_bytes: int = _PTYCHO_FULL_MAX_BYTES) -> None:
|
||||
"""Copy src to dst; if src is larger than max_bytes, save a downscaled
|
||||
PNG instead (halving the pixel area each step) so the upload server
|
||||
accepts it. Without Pillow, or if it can't get small enough, the
|
||||
plain/smallest copy is kept."""
|
||||
if src.stat().st_size <= max_bytes or not _PIL_AVAILABLE:
|
||||
shutil.copy2(src, dst)
|
||||
return
|
||||
img = _PILImage.open(src)
|
||||
img.load()
|
||||
w, h = img.size
|
||||
scale = 1.0
|
||||
while True:
|
||||
scale *= 0.7071
|
||||
size = (max(1, int(w * scale)), max(1, int(h * scale)))
|
||||
img.resize(size, _PILImage.LANCZOS).save(dst, "PNG", optimize=True)
|
||||
if dst.stat().st_size <= max_bytes or min(size) <= 64:
|
||||
return
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Subclass hooks
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Full-size ptycho PNGs copied for upload are capped in size: the upload
|
||||
server rejected large (stitched) probe/spectrum images with HTTP 413."""
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
PIL = pytest.importorskip("PIL.Image")
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.OMNY_shared.webpage_generator_base import (
|
||||
WebpageGeneratorBase,
|
||||
)
|
||||
|
||||
|
||||
def test_large_png_is_downscaled_below_cap(tmp_path):
|
||||
src, dst = tmp_path / "S00085-S00093_probe.png", tmp_path / "out.png"
|
||||
PIL.fromarray((np.random.default_rng(0).random((800, 1200, 3)) * 255).astype("uint8")).save(src)
|
||||
cap = 300_000
|
||||
assert src.stat().st_size > cap
|
||||
|
||||
WebpageGeneratorBase._copy_full_capped(src, dst, max_bytes=cap)
|
||||
|
||||
assert dst.stat().st_size <= cap
|
||||
assert PIL.open(dst).size[0] < 1200
|
||||
|
||||
|
||||
def test_small_png_is_copied_unchanged(tmp_path):
|
||||
src, dst = tmp_path / "S00001_phase.png", tmp_path / "out.png"
|
||||
PIL.new("RGB", (50, 50)).save(src)
|
||||
|
||||
WebpageGeneratorBase._copy_full_capped(src, dst, max_bytes=300_000)
|
||||
|
||||
assert dst.read_bytes() == src.read_bytes()
|
||||
Reference in new issue
Block a user