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()