Skip to main content

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 a pyldt.Ldt object 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

Metadata

Release files for eulumdat-symmetry 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for eulumdat-symmetry 1.0.0
File Size Uploaded
eulumdat_symmetry-1.0.0.tar.gz 20.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for eulumdat-symmetry 1.0.0
File Interpreter ABI Platform
eulumdat_symmetry-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 38.7 kB

Release files / eulumdat_symmetry-1.0.0.tar.gz

Download URL eulumdat_symmetry-1.0.0.tar.gz
Size 20.5 kB
Tags Source
SHA-256 checksum
How to use checksums
1e81c37dbd32eea4c5df6c642f5461ef51e2bcfa2d2b569aca8c71b85f42e0c2
BLAKE2b-256 checksum
How to use checksums
bda69d9fb0efc36459496813e82c32f6568235ad8322782d82936706fc58070a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.11

Release files / eulumdat_symmetry-1.0.0-py3-none-any.whl

Download URL eulumdat_symmetry-1.0.0-py3-none-any.whl
Size 18.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
db6b4e69ccacccbbbdfd546f2b43c7f7b99ba6414d8697dd2b373fe5b8500e1c
BLAKE2b-256 checksum
How to use checksums
f127531d9c59f8a5cca6812a79866728ad21470b4c5a07852c410f71a9e91c29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.11

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page