Files
Jungfraujoch/docs/landing/hero_frame.py
T
leonarski_f f960bd7a80 Docs landing page: a real diffraction frame in the hero, and the command with a model
The generated spot-and-ring drawing is replaced by one 0.2 deg rotation frame of the in-house
lysozyme standard (EIGER2 16M, SLS X10SA): Bragg spots on their lunes, the beam stop and its arm,
the module gaps. docs/landing/hero_frame.py makes the 920 px WebP (67 KB) from the master
file - crop about the beam centre, log tone map from the frame's own background, block mean+max
downsampling - and is run by hand; the WebP is committed and copied by the landing-page step.
The dark theme shows it inverted through a CSS filter.

The hero's command line drops its explanatory sentence and gains the second form,
rugnux <diffraction image file> <CIF/PDB model file>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit ff26000bfe7446cb4fda739c3b72596f8a8b7047)
2026-10-26 10:53:25 +01:00

59 lines
2.3 KiB
Python

#!/usr/bin/env python3
"""Make the front page's hero image (docs/landing/hero_frame.webp) from one frame of an NXmx
HDF5 dataset. Not part of the documentation build: run it by hand when the image is to change,
and commit the WebP.
hero_frame.py <master.h5> [frame] [out.webp]
The frame is cropped to a square about the beam centre (from the master file), tone-mapped with
a log curve set from its own background level, and downsampled with a block mean+max blend so
the Bragg spots stay visible at a ninth of the size. Masked pixels (module gaps, bad pixels) are
drawn light grey. Dark spots on a light background; the page inverts it for the dark theme.
Needs numpy, h5py, hdf5plugin and Pillow.
"""
import sys
import h5py
import hdf5plugin # noqa: F401 - registers the bitshuffle/LZ4 filters for h5py
import numpy as np
from PIL import Image
OUT_PX = 920 # twice the largest size the hero shows it at
CROP = 0.68 # fraction of the largest square about the beam centre: larger spots, fewer gaps
master = sys.argv[1]
frame = int(sys.argv[2]) if len(sys.argv) > 2 else 0
out = sys.argv[3] if len(sys.argv) > 3 else "hero_frame.webp"
with h5py.File(master, "r") as f:
data = f["/entry/data"]
dset = data["data"] if "data" in data else data["data_000001"]
img = dset[frame].astype(np.float64)
det = f["/entry/instrument/detector"]
cx, cy = float(det["beam_center_x"][()]), float(det["beam_center_y"][()])
bad = img >= 2**31 # masked and gap pixels carry the all-ones value
img[bad] = 0
# Square crop about the beam centre, a fraction of the largest the detector allows, in whole
# output pixels.
k = int(CROP * 2 * min(cx, cy, img.shape[1] - cx, img.shape[0] - cy)) // OUT_PX
half = k * OUT_PX // 2
x0, y0 = int(cx) - half, int(cy) - half
img = img[y0:y0 + 2 * half, x0:x0 + 2 * half]
bad = bad[y0:y0 + 2 * half, x0:x0 + 2 * half]
# Log tone map: the background sits at a light grey, spots at 20x background are black.
bkg = max(np.median(img[~bad]), 1.0)
t = np.clip(np.log1p(img / (1.2 * bkg)) / np.log1p(20.0), 0, 1)
n = OUT_PX
t = t.reshape(n, k, n, k)
gap = bad.reshape(n, k, n, k).mean(axis=(1, 3)) > 0.5
v = 0.3 * t.mean(axis=(1, 3)) + 0.7 * t.max(axis=(1, 3))
g = (255 * (1 - 0.92 * v)).astype(np.uint8)
g[gap] = 238
Image.fromarray(g).save(out, quality=82, method=6)
print(out, "beam centre", cx, cy, "crop", 2 * half, "px, block", k, "background", bkg)