diff --git a/public/brand/users/anesthesiasimulation.png b/public/brand/users/anesthesiasimulation.png new file mode 100644 index 0000000..7021627 Binary files /dev/null and b/public/brand/users/anesthesiasimulation.png differ diff --git a/public/brand/users/atomicateam.png b/public/brand/users/atomicateam.png new file mode 100644 index 0000000..c0fe0af Binary files /dev/null and b/public/brand/users/atomicateam.png differ diff --git a/public/brand/users/bgu-aidnd.png b/public/brand/users/bgu-aidnd.png new file mode 100644 index 0000000..81dc910 Binary files /dev/null and b/public/brand/users/bgu-aidnd.png differ diff --git a/public/brand/users/cmu-db.png b/public/brand/users/cmu-db.png index 1a9634a..f12fb30 100644 Binary files a/public/brand/users/cmu-db.png and b/public/brand/users/cmu-db.png differ diff --git a/public/brand/users/codecentric.png b/public/brand/users/codecentric.png index 26f0dc6..15931f8 100644 Binary files a/public/brand/users/codecentric.png and b/public/brand/users/codecentric.png differ diff --git a/public/brand/users/curtin-timescales-of-mineral-systems.png b/public/brand/users/curtin-timescales-of-mineral-systems.png new file mode 100644 index 0000000..5ed7961 Binary files /dev/null and b/public/brand/users/curtin-timescales-of-mineral-systems.png differ diff --git a/public/brand/users/emsl-computing.png b/public/brand/users/emsl-computing.png index d134785..bb41529 100644 Binary files a/public/brand/users/emsl-computing.png and b/public/brand/users/emsl-computing.png differ diff --git a/public/brand/users/gemseo.png b/public/brand/users/gemseo.png index 803e789..e08202c 100644 Binary files a/public/brand/users/gemseo.png and b/public/brand/users/gemseo.png differ diff --git a/public/brand/users/ibm.png b/public/brand/users/ibm.png new file mode 100644 index 0000000..14d6e44 Binary files /dev/null and b/public/brand/users/ibm.png differ diff --git a/public/brand/users/icb-dcm.png b/public/brand/users/icb-dcm.png index 11500d4..7d30c01 100644 Binary files a/public/brand/users/icb-dcm.png and b/public/brand/users/icb-dcm.png differ diff --git a/public/brand/users/icrar.png b/public/brand/users/icrar.png index a2366b9..f7608eb 100644 Binary files a/public/brand/users/icrar.png and b/public/brand/users/icrar.png differ diff --git a/public/brand/users/industrial-optimization-group.png b/public/brand/users/industrial-optimization-group.png new file mode 100644 index 0000000..2733469 Binary files /dev/null and b/public/brand/users/industrial-optimization-group.png differ diff --git a/public/brand/users/lanl.png b/public/brand/users/lanl.png index c17cc50..da138db 100644 Binary files a/public/brand/users/lanl.png and b/public/brand/users/lanl.png differ diff --git a/public/brand/users/llnl.png b/public/brand/users/llnl.png index b37e4c8..f975e4c 100644 Binary files a/public/brand/users/llnl.png and b/public/brand/users/llnl.png differ diff --git a/public/brand/users/nanograv.png b/public/brand/users/nanograv.png new file mode 100644 index 0000000..e82bcd6 Binary files /dev/null and b/public/brand/users/nanograv.png differ diff --git a/public/brand/users/nasa.png b/public/brand/users/nasa.png index d2151c2..a101449 100644 Binary files a/public/brand/users/nasa.png and b/public/brand/users/nasa.png differ diff --git a/public/brand/users/national-digital-twin.png b/public/brand/users/national-digital-twin.png index bfa0d25..5210425 100644 Binary files a/public/brand/users/national-digital-twin.png and b/public/brand/users/national-digital-twin.png differ diff --git a/public/brand/users/openmdao.png b/public/brand/users/openmdao.png index a0add32..d411a29 100644 Binary files a/public/brand/users/openmdao.png and b/public/brand/users/openmdao.png differ diff --git a/public/brand/users/openworm.png b/public/brand/users/openworm.png index 775a373..a2642b7 100644 Binary files a/public/brand/users/openworm.png and b/public/brand/users/openworm.png differ diff --git a/public/brand/users/paypal.png b/public/brand/users/paypal.png new file mode 100644 index 0000000..e50e0a2 Binary files /dev/null and b/public/brand/users/paypal.png differ diff --git a/public/brand/users/philipmorrisintl.png b/public/brand/users/philipmorrisintl.png index 37452cd..7482905 100644 Binary files a/public/brand/users/philipmorrisintl.png and b/public/brand/users/philipmorrisintl.png differ diff --git a/public/brand/users/qubole.png b/public/brand/users/qubole.png index da998e8..4f79e10 100644 Binary files a/public/brand/users/qubole.png and b/public/brand/users/qubole.png differ diff --git a/public/brand/users/sandialabs.png b/public/brand/users/sandialabs.png index e4da5b6..893d2b4 100644 Binary files a/public/brand/users/sandialabs.png and b/public/brand/users/sandialabs.png differ diff --git a/public/brand/users/seduceproject.png b/public/brand/users/seduceproject.png new file mode 100644 index 0000000..2bc1582 Binary files /dev/null and b/public/brand/users/seduceproject.png differ diff --git a/public/brand/users/smtorg.png b/public/brand/users/smtorg.png index d016ca3..e0f7024 100644 Binary files a/public/brand/users/smtorg.png and b/public/brand/users/smtorg.png differ diff --git a/public/brand/users/terrapower.png b/public/brand/users/terrapower.png new file mode 100644 index 0000000..129d222 Binary files /dev/null and b/public/brand/users/terrapower.png differ diff --git a/public/brand/users/wwu-mmll.png b/public/brand/users/wwu-mmll.png index f695406..f153ba4 100644 Binary files a/public/brand/users/wwu-mmll.png and b/public/brand/users/wwu-mmll.png differ diff --git a/src/components/content.ts b/src/components/content.ts index fed6327..fbdfd15 100644 --- a/src/components/content.ts +++ b/src/components/content.ts @@ -49,7 +49,7 @@ export function mountUsedBy(root: ParentNode = document): void { + alt="${d.name}" height="68" loading="lazy" decoding="async"> `, ).join('') } diff --git a/src/data/used-by.ts b/src/data/used-by.ts index def34a0..8ffacc8 100644 --- a/src/data/used-by.ts +++ b/src/data/used-by.ts @@ -1,19 +1,28 @@ /** * Organisations that build on these packages. * - * Every entry was verified against GitHub's live dependency graph, one - * repository at a time: either the SBOM for that repository lists the package, - * or its manifest on the default branch declares it. Entries that only appeared - * in the dependents *index* did not survive — that index lags, and several of - * them had since dropped the dependency or commented it out. Anything that - * could not be confirmed was left off, however good the name would have looked. + * Most entries were verified against the package manifests on the repository's + * default branch: a `requirements*.txt`, `setup.py`, `setup.cfg` or + * `pyproject.toml` that names the package. Entries that only appeared in + * GitHub's dependents *index* did not survive — that index lags, and several of + * them had since dropped the dependency or commented it out. * - * Only pydoe and pyswarm have organisational users. mcerp and soerp have - * dependents, but individuals rather than organisations. + * The exception is the block marked below, taken from the "Who uses PyDOE?" + * page in the pydoe documentation. Those repositories no longer declare pyDOE + * on their default branch, so they are kept on the strength of that page rather + * than a manifest we can point at today. * - * Logos are the organisations' own GitHub avatars, desaturated at build time - * rather than in CSS so the strip stays even and nothing flashes colour while - * it loads. + * pyDOE2 and pyDOE3 count as pyDOE: both forks have been merged back and are + * maintained here, so a manifest naming either one is a pyDOE user. + * + * pyswarm is matched on a word boundary so that `pyswarms` — an unrelated + * package by a different author — never counts. + * + * Logos are the organisations' own marks, desaturated and normalised to a single + * height by `tools/build-user-logos.py`, so the strip stays even and nothing + * flashes colour while it loads. Orgs whose avatar is a default identicon, a + * photograph, or an image that turns to mush in greyscale are left off rather + * than shown badly. */ export interface Dependent { @@ -43,4 +52,34 @@ export const USED_BY: Dependent[] = [ { login: 'ICB-DCM', name: 'ICB-DCM', repo: 'ICB-DCM/pyPESTO', uses: 'pyswarm' }, { login: 'openworm', name: 'OpenWorm', repo: 'openworm/ChannelWorm', uses: 'pyswarm' }, { login: 'wwu-mmll', name: 'Medical Machine Learning Lab', repo: 'wwu-mmll/photonai', uses: 'pyDOE' }, + + // University research groups. + { + login: 'industrial-optimization-group', + name: 'Multiobjective Optimization Group, University of Jyväskylä', + repo: 'industrial-optimization-group/pyRVEA', + uses: 'pyDOE', + }, + { + login: 'Curtin-Timescales-of-Mineral-Systems', + name: 'Timescales of Mineral Systems, Curtin University', + repo: 'Curtin-Timescales-of-Mineral-Systems/UPb-Unmixer', + uses: 'soerp', + }, + { login: 'BGU-AiDnD', name: 'AiDnD, Ben-Gurion University', repo: 'BGU-AiDnD/Debugger', uses: 'pyswarm' }, + { login: 'SeduceProject', name: 'SeDuCe', repo: 'SeduceProject/seduce_ml', uses: 'mcerp' }, + { login: 'atomicateam', name: 'Atomica', repo: 'atomicateam/atomica', uses: 'pyswarm' }, + { + login: 'AnesthesiaSimulation', + name: 'Anesthesia Simulation', + repo: 'AnesthesiaSimulation/Python_Anesthesia_Simulator', + uses: 'pyswarm', + }, + + // From the "Who uses PyDOE?" page in the pydoe docs. Not currently declared + // in these repositories' manifests — see the note at the top of this file. + { login: 'ibm', name: 'IBM', repo: 'ibm/simulai', uses: 'pyDOE' }, + { login: 'paypal', name: 'PayPal', repo: 'paypal/gators', uses: 'pyDOE' }, + { login: 'terrapower', name: 'TerraPower', repo: 'terrapower/armi', uses: 'pyDOE' }, + { login: 'nanograv', name: 'NANOGrav', repo: 'nanograv/holodeck', uses: 'pyDOE' }, ] diff --git a/src/styles/components.css b/src/styles/components.css index 1cb17b1..c416961 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -863,15 +863,20 @@ .used-by { display: inline-flex; align-items: center; - padding: 0.4rem 1.15rem; + padding: 0.4rem 1.35rem; } +/* + * Every logo is delivered at one height, so the strip needs a single `height` + * here and nothing else. The width cap is for the occasional wordmark that is + * much wider than it is tall. + */ .used-by__logo { width: auto; - height: 52px; - max-width: 170px; + height: 68px; + max-width: 210px; object-fit: contain; - opacity: 0.55; + opacity: 0.62; transition: opacity var(--dur-base) var(--ease-out); } diff --git a/tools/build-user-logos.py b/tools/build-user-logos.py new file mode 100644 index 0000000..36a68ca --- /dev/null +++ b/tools/build-user-logos.py @@ -0,0 +1,84 @@ +""" +Build the "Used by" logo strip. + +Each source logo — an organisation's GitHub avatar, or replacement artwork +dropped into the source directory — is flattened onto white, desaturated, and +scaled to one content height. Desaturating here rather than in CSS keeps the +strip even and stops anything flashing colour while it loads; the single height +lands every logo on one optical line instead of a row of mismatched sizes. + + python tools/build-user-logos.py [--out public/brand/users] + +Source files are named `.`, lowercased; the site looks the +logo up by login. Orgs whose avatar is a default identicon, a photograph, or an +image that turns to mush in greyscale are better left out than shown badly. +""" + +import argparse +import os +import sys + +import cv2 +import numpy as np + +HEIGHT = 128 # content height in px — 2x the CSS height, for retina +MAX_WIDTH = 420 # width cap, so a long wordmark cannot take over the strip + + +def load(path): + """Read an image path that may contain non-ASCII characters on Windows.""" + im = cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_UNCHANGED) + if im is None: + raise SystemExit(f"unreadable: {path}") + if im.ndim == 2: + im = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR) + if im.shape[2] == 4: + return im[:, :, :3], im[:, :, 3] + return im, None + + +def process(src, dest): + bgr, alpha = load(src) + + # Avatars ship an alpha channel that only rounds the corners, and the page + # behind the strip is white — so flatten onto white and keep the square. + if alpha is not None: + a = (alpha.astype(np.float32) / 255.0)[:, :, None] + bgr = (bgr.astype(np.float32) * a + 255.0 * (1 - a)).astype(np.uint8) + + gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY) + + h, w = gray.shape + scale = min(HEIGHT / h, MAX_WIDTH / w) + size = (max(1, round(w * scale)), max(1, round(h * scale))) + interp = cv2.INTER_AREA if scale < 1 else cv2.INTER_CUBIC + gray = cv2.resize(gray, size, interpolation=interp) + + ok, buf = cv2.imencode(".png", cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR), + [cv2.IMWRITE_PNG_COMPRESSION, 9]) + if not ok: + raise SystemExit(f"could not encode: {dest}") + buf.tofile(dest) + return size + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("source", help="directory of . logo files") + ap.add_argument("--out", default=os.path.join("public", "brand", "users")) + args = ap.parse_args() + + os.makedirs(args.out, exist_ok=True) + names = sorted(f for f in os.listdir(args.source) + if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))) + if not names: + sys.exit(f"no images in {args.source}") + + for name in names: + login = os.path.splitext(name)[0].lower() + w, h = process(os.path.join(args.source, name), os.path.join(args.out, f"{login}.png")) + print(f" {login:38} {w:4}x{h:<4}") + + +if __name__ == "__main__": + main()