PyCoupole
GPU-accelerated Monte Carlo uncertainty quantification for SVBRDF measurements on the La Coupole setup. Companion code to the associated Optics Express paper, Supplemental Document and measured dataset.
Installation
[!important] This code is built on NVIDIA's Pythonic, GPU-friendly library Warp, version 1.14.0 or newer. Python version 3.10 or newer is required. Warp can run on x86-64 and ARMv8 CPUs on Windows and Linux. macOS requires Apple Silicon (ARM64). GPU support requires a CUDA-capable NVIDIA GPU and driver (minimum GeForce GTX 9xx).
Simply install it from PyPI:
pip install pycoupole
Quick start
Four functions are exposed, in two pairs:
computeBRDFDeterministic/computeBRDFDistributiontake the measurement geometry as explicit arguments.computeBRDFDeterministicFromCSV/computeBRDFDistributionFromCSVread that geometry directly from a measurement CSV (the typical entry point).
From a measurement CSV
Most of the time the inputs come from a measurement CSV, where each row is one pixel-channel sample. Rows are addressed by zero-based index (the header is not counted).
import pycoupole
# Deterministic estimate on selected rows -> list of (rowIndex, brdf in sr^-1)
# Pass a single int for one row, an iterable for several, or None for all rows
results = pycoupole.computeBRDFDeterministicFromCSV("measurements.csv", rows=[0, 1, 2])
for index, brdf in results:
print(f"row {index}: {brdf:.6e}")
# Monte Carlo distribution on a single row.
result = pycoupole.computeBRDFDistributionFromCSV("measurements.csv", row=0)
pdf = result["pdf"] # density, in sr
brdfValues = result["brdfValues"] # bin centres, in sr^-1
The distribution helper takes exactly one row: a single distribution already saturates the GPU, so for several rows you loop in Python over the row indices.
From explicit arguments
When the geometry does not come from a CSV, call the underlying functions directly.
import pycoupole
sample = dict(
pixelID = (1521, 915),
channelID = 1,
pixelValueLSB = 10,
exposureTimeS = 0.122374,
lightCenter = (-1.14251, 0.507025, 0.391015),
lightNormal = ( 0.965928, 0.00172073, -0.258806),
triangleCenter = ( 0.490908, -0.0674868, 0.0267361),
triangleNormal = ( 0.0135288,-0.00660755, 0.999887),
)
value = pycoupole.computeBRDFDeterministic(**sample) # point estimate, sr^-1
result = pycoupole.computeBRDFDistribution(**sample) # MC distribution
Both *FromCSV functions accept a device argument and forward extra keyword
arguments (nbBins, batchSize, seed, ...) to the underlying estimators.
Interpreting the output
computeBRDFDistribution returns a probability density, not a histogram of
counts. The density carries the inverse unit of the BRDF (sr), and integrates
to 1 over its support — so its values are not bounded by 1 and will be large
for a sharply peaked distribution. Recover the per-bin probability mass, then
the moments and credible intervals, as:
binWidth = brdfValues[1] - brdfValues[0]
mass = pdf * binWidth # sums to 1
mean = float(np.sum(mass * brdfValues))
std = float(np.sqrt(np.sum(mass * (brdfValues - mean) ** 2)))
cdf = np.cumsum(mass)
median = float(np.interp(0.50, cdf, brdfValues))
ci95_low = float(np.interp(0.025, cdf, brdfValues))
ci95_up = float(np.interp(0.975, cdf, brdfValues))
For a well-conditioned acquisition the deterministic value sits near the centre
of the distribution and the out-of-range fraction (1 - nbAccepted / nbTotal)
is negligible.
Citation
If you use this software, please cite the associated paper as below (see also CITATION.cff).
La Coupole: an SVBRDF measurement device for large and non-planar objects. Antoine Lucat, Pierre Mézières, François Margall, Louis De Oliveira, Marjorie Paillet, Arnaud Tizon, Pierre Bénard, Romain Pacanowski. Optics Express, Vol. 34, Issue 7, pp. 11695-11709 (March 2026). DOI: 10.1364/OE.587877.
Associated BibTeX entry
@article{Lucat:26,
author = {Antoine Lucat and Pierre M\'{e}zi\`{e}res and Fran\c{c}ois Margall and Louis De Oliveira and Marjorie Paillet and Arnaud Tizon and Pierre B\'{e}nard and Romain Pacanowski},
journal = {Optics Express},
keywords = {Camera calibration; Imaging systems; Light sources; Physiology; Printed circuit boards; Spatial resolution},
number = {7},
pages = {11695--11709},
publisher = {Optica Publishing Group},
title = {La Coupole: an SVBRDF measurement device for large and non-planar objects},
volume = {34},
month = {Apr},
year = {2026},
url = {https://opg.optica.org/oe/abstract.cfm?URI=oe-34-7-11695},
doi = {10.1364/OE.587877},
}
[!note] For a detailed derivation of the uncertainty estimation method, including assumptions, propagation steps, and validation, see Section 3 (pp. 14–20) of the associated Supplemental Material (DOI: 10.6084/m9.figshare.31418393.v1).
License
PyCoupole is distributed under the MIT License. See LICENSE.txt for more information.
Metadata
Release files for pycoupole 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pycoupole-0.2.1.tar.gz | 15.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pycoupole-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.7 kB
Release files / pycoupole-0.2.1.tar.gz
| Download URL | pycoupole-0.2.1.tar.gz |
|---|---|
| Size | 15.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d13b69396c7ffd4855194feb47bb39ce630f95de045d39408e5ce22a6e98002
|
|
BLAKE2b-256 checksum How to use checksums |
b0fe459529edf57f041fba13b26872709c9232331d216357463932aaf82ec845
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.20
|
Release files / pycoupole-0.2.1-py3-none-any.whl
| Download URL | pycoupole-0.2.1-py3-none-any.whl |
|---|---|
| Size | 14.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d7b2799106e062b2527be8ffc4590f1adccdfadd766388a22e8048cadd20be58
|
|
BLAKE2b-256 checksum How to use checksums |
52843605d219e6350426f614353cf2e7c9e0a97f387d2e47f0ab519ba8140067
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.20
|