Metadata-Version: 2.4
Name: pycoupole
Version: 0.2.2
Summary: GPU-accelerated Monte Carlo uncertainty quantification for SVBRDF measurements on the La Coupole setup. Companion code to the associated Optics Express paper and dataset.
Author-email: François Margall <francois.margall@inria.fr>
License-Expression: MIT
Project-URL: Homepage, https://lacoupole.gitlabpages.inria.fr
Project-URL: Repository, https://gitlab.inria.fr/lacoupole/pycoupole
Project-URL: Tracker, https://gitlab.inria.fr/lacoupole/pycoupole/-/boards
Project-URL: Article, https://doi.org/10.1364/OE.587877
Project-URL: Supplemental, https://doi.org/10.6084/m9.figshare.31418393.v1
Project-URL: Dataset, https://doi.org/10.57745/TWXE9E
Keywords: monte-carlo,nvidia,uncertainty,metrology,warp,brdf,gum,coupole
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Development Status :: 5 - Production/Stable
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: numpy
Requires-Dist: warp-lang>=1.14.0
Dynamic: license-file

# PyCoupole

GPU-accelerated Monte Carlo uncertainty quantification for SVBRDF measurements
on the La Coupole setup. Companion code to [the associated Optics Express paper](https://doi.org/10.1364/OE.587877), 
[Supplemental Document](https://doi.org/10.6084/m9.figshare.31418393.v1)
and [dataset](https://doi.org/10.57745/TWXE9E).

## 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` / `computeBRDFDistribution` take the measurement
  geometry as explicit arguments.
- `computeBRDFDeterministicFromCSV` / `computeBRDFDistributionFromCSV` read 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).

```python
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.

> [!tip] 
> You can access the measurements associated to the project on the 
> [public archive](https://doi.org/10.57745/TWXE9E). Filter the
> file type by `Tabular Data`, and select one or several of the `.tab`
> files, and download them in their `Original Format` or as `Comma Separated Values`.
> You can also use the direct links below:

<details>
<summary>List of SVBRDF datasets</summary>

- [Aïnou coat](https://doi.org/10.57745/1EKDL0)
- [Alabaster](https://doi.org/10.57745/ZWTDB5)
- [Fish skin coat](https://doi.org/10.57745/DFT1Q5)
- [General apron (Pt. 1)](https://doi.org/10.57745/IGTJDE)
- [General apron (Pt. 2)](https://doi.org/10.57745/BSTZYC)
- [Prophylactic shirt](https://doi.org/10.57745/RUDI3J)
- [Wedding vest](https://doi.org/10.57745/5DGJK9)

</details>

### From explicit arguments

When the geometry does not come from a CSV, call the underlying functions
directly.

```python
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:

```python
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 either this software of the dataset, please cite the associated paper as below (see also [CITATION.cff](./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](https://doi.org/10.1364/OE.587877).

<details>
<summary>Associated BibTeX entry</summary>

```bibtex
@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},
}
```

</details>

> [!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](https://doi.org/10.6084/m9.figshare.31418393.v1)).

## License

PyCoupole is distributed under the MIT License. See [LICENSE.txt](./LICENSE.txt) for more information.
