Symmetrisation of EULUMDAT (.ldt) photometric files — extension to eulumdat-py.
Project description
eulumdat-symmetry
An extension to pyldt for symmetrising EULUMDAT (.ldt) photometric files.
Developed in an ISO 17025 accredited photometry laboratory.
Overview
EULUMDAT files store luminous intensity distributions as a [mc × ng] matrix of C-planes and γ-angles. When a luminaire has a symmetric distribution, the file can declare this via the ISYM field (1–4), which allows lighting design software to reconstruct the full distribution from a subset of C-planes.
This library provides two tools:
LdtSymmetriser— symmetrise apyldt.Ldtobject for a known ISYM mode. Deterministic, no configuration needed.LdtAutoDetector— automatically detect the most plausible ISYM mode from the shape of the distribution. Optional; use only when the mode is not known in advance.
Installation
pip install eulumdat-symmetry
Requires eulumdat-py >= 0.1.4 (pyldt).
ISYM codes
| ISYM | Description | C-planes symmetrised |
|---|---|---|
| 0 | No symmetry | — |
| 1 | Full rotational symmetry | All planes averaged to a single profile |
| 2 | Symmetry about C0–C180 | C and 360°−C averaged |
| 3 | Symmetry about C90–C270 | C and 180°−C averaged |
| 4 | Quadrant symmetry | All 4 quadrant mirrors averaged |
Usage
Known symmetry mode
from pyldt import LdtReader, LdtWriter
from ldt_symmetry import LdtSymmetriser
ldt = LdtReader.read("luminaire.ldt")
ldt_sym = LdtSymmetriser.symmetrise(ldt, isym=2)
LdtWriter.write(ldt_sym, "luminaire_SYM.ldt")
LdtSymmetriser.symmetrise() returns a new Ldt object — the original is never modified.
If the file already declares a non-zero ISYM, the function returns an unchanged copy by default (the distribution has already been symmetrised at measurement time). Use force=True to re-symmetrise regardless.
ldt_sym = LdtSymmetriser.symmetrise(ldt, isym=2, force=True)
Automatic detection
from pyldt import LdtReader, LdtWriter
from ldt_symmetry import LdtAutoDetector, LdtSymmetriser
ldt = LdtReader.read("luminaire.ldt")
detector = LdtAutoDetector()
isym = detector.detect(ldt)
if isym != 0:
ldt_sym = LdtSymmetriser.symmetrise(ldt, isym=isym)
LdtWriter.write(ldt_sym, "luminaire_SYM.ldt")
else:
print("No plausible symmetry detected — file left unchanged.")
Automatic detection with diagnostic
When return_diag=True, detect() returns a (isym, diag) tuple. The diag dict contains the shape metrics, symmetry scores, and the veto decision — useful for understanding why a particular mode was accepted or rejected.
isym, diag = detector.detect(ldt, return_diag=True)
print(f"Detected ISYM: {isym}")
print(f"Candidate ISYM (shape stage): {diag['isym_candidate']}")
print(f"Accepted: {diag['accepted']}")
print(f"Scores: {diag['scores']}")
# diag['scores'] = {1: 0.12, 2: 0.08, 3: 0.31, 4: 0.31}
# diag['score_threshold'] = 0.23
Batch processing
from pathlib import Path
from pyldt import LdtReader, LdtWriter
from ldt_symmetry import LdtAutoDetector, LdtSymmetriser
detector = LdtAutoDetector()
for path in Path("ldt_files").glob("*.ldt"):
ldt = LdtReader.read(path)
isym = detector.detect(ldt)
if isym != 0:
ldt_sym = LdtSymmetriser.symmetrise(ldt, isym=isym)
out = path.with_stem(path.stem + "_SYM")
LdtWriter.write(ldt_sym, out)
print(f"{path.name} → ISYM {isym} → {out.name}")
else:
print(f"{path.name} → no symmetry detected")
How automatic detection works
Detection runs in two stages:
Stage 1 — Shape analysis
The intensity distribution is projected onto the floor (γ < 90°) and ceiling (γ > 90°) planes using the point-source illuminance formula E = I·cos³(γ)/d². Weighted moments (centroid offset, ellipse anisotropy, principal axis orientation) and angular harmonics (H4 for quadrant detection) are computed for each hemisphere. A candidate mode is chosen by combining the decisions from both hemispheres.
Stage 2 — Score veto
The relative RMS asymmetry is measured for the candidate mode. If the score exceeds the threshold, the candidate is rejected and ISYM 0 is returned. This prevents the shape stage from proposing a symmetric mode when the distribution is actually irregular.
Both stages must agree for a mode to be accepted.
Validation
The detection algorithm was validated on a dataset of 42 real manufacturer LDT files covering all five ISYM modes (0–4), selected for their representativeness from several hundred laboratory measurements. No misclassification was observed (42/42 correct).
Adjusting detection thresholds
The default thresholds work well for typical manufacturer files. If you observe incorrect decisions on your dataset, you can adjust them at instantiation:
detector = LdtAutoDetector(
score_th=0.20, # stricter veto for ISYM 2/3/4 (default: 0.23)
rot_score_th=0.15, # stricter veto for ISYM 1 (default: 0.20)
)
See the LdtAutoDetector docstring for the full list of parameters.
Project structure
ldt_symmetry/
├── __init__.py ← Public API (LdtSymmetriser, LdtAutoDetector)
├── symmetriser.py ← LdtSymmetriser
├── auto_detector.py ← LdtAutoDetector
└── _geometry.py ← Internal geometry and scoring functions
Relation to pyldt
eulumdat-symmetry is an extension to pyldt, not a replacement. It operates on pyldt.Ldt objects and delegates all file I/O to pyldt.LdtReader and pyldt.LdtWriter.
| Task | Tool |
|---|---|
Read / write .ldt files |
pyldt (LdtReader, LdtWriter) |
| Edit header fields | pyldt (LdtHeader) |
| Symmetrise the intensity distribution | eulumdat-symmetry (LdtSymmetriser) |
| Detect the symmetry mode automatically | eulumdat-symmetry (LdtAutoDetector) |
Source code
github.com/123VincentB/ldt_symmetry
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eulumdat_symmetry-1.0.0.tar.gz.
File metadata
- Download URL: eulumdat_symmetry-1.0.0.tar.gz
- Upload date:
- Size: 20.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e81c37dbd32eea4c5df6c642f5461ef51e2bcfa2d2b569aca8c71b85f42e0c2
|
|
| MD5 |
91e688e8d37d343aa8a0e7332f389649
|
|
| BLAKE2b-256 |
bda69d9fb0efc36459496813e82c32f6568235ad8322782d82936706fc58070a
|
File details
Details for the file eulumdat_symmetry-1.0.0-py3-none-any.whl.
File metadata
- Download URL: eulumdat_symmetry-1.0.0-py3-none-any.whl
- Upload date:
- Size: 18.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db6b4e69ccacccbbbdfd546f2b43c7f7b99ba6414d8697dd2b373fe5b8500e1c
|
|
| MD5 |
8584037de8a4a12cc0606e3804281792
|
|
| BLAKE2b-256 |
f127531d9c59f8a5cca6812a79866728ad21470b4c5a07852c410f71a9e91c29
|