@photivra/engine 2.16.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +24 -0
- package/dist/camera/lens-projection.d.ts +5 -0
- package/dist/camera/lens-projection.js +10 -0
- package/dist/camera/lens-projection.js.map +1 -1
- package/dist/camera/sensor-equivalence.d.ts +156 -0
- package/dist/camera/sensor-equivalence.js +208 -0
- package/dist/camera/sensor-equivalence.js.map +1 -0
- package/dist/capture/native-environment-tabulated.d.ts +5 -0
- package/dist/capture/native-environment-tabulated.js +1 -1
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-lens-catalogue.d.ts +227 -0
- package/dist/equipment/generic-lens-catalogue.js +243 -0
- package/dist/equipment/generic-lens-catalogue.js.map +1 -0
- package/dist/equipment/ideal-lens.d.ts +25 -0
- package/dist/equipment/ideal-lens.js +21 -0
- package/dist/equipment/ideal-lens.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +94 -0
- package/dist/facade/catalogue-lens-signal.js +362 -0
- package/dist/facade/catalogue-lens-signal.js.map +1 -0
- package/dist/facade/defocus-bokeh-zones.d.ts +33 -0
- package/dist/facade/defocus-bokeh-zones.js +53 -0
- package/dist/facade/defocus-bokeh-zones.js.map +1 -0
- package/dist/facade/defocus-image-space-provider.d.ts +9 -0
- package/dist/facade/defocus-image-space-provider.js +101 -9
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +29 -0
- package/dist/facade/diffraction-provider.js +117 -11
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +4 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +10 -0
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +61 -0
- package/dist/facade/display-view-lens-signal.js +502 -0
- package/dist/facade/display-view-lens-signal.js.map +1 -0
- package/dist/facade/expected-signal-setup.d.ts +15 -0
- package/dist/facade/expected-signal-setup.js +19 -2
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +13 -0
- package/dist/facade/expected-signal.js +155 -1
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +7 -0
- package/dist/facade/generic-lens-capture.js +9 -0
- package/dist/facade/generic-lens-capture.js.map +1 -0
- package/dist/facade/large-defocus-lattice.d.ts +39 -0
- package/dist/facade/large-defocus-lattice.js +64 -0
- package/dist/facade/large-defocus-lattice.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +5 -0
- package/dist/facade/scene-radiance.js +5 -2
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +67 -7
- package/dist/facade/simulate-capture.js +400 -64
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +23 -17
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +0 -9
- package/dist/gpu/display-view-quality-gpu.js +6 -0
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.js +2 -0
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/optics/defocus-bokeh-kernel.d.ts +207 -0
- package/dist/optics/defocus-bokeh-kernel.js +380 -0
- package/dist/optics/defocus-bokeh-kernel.js.map +1 -0
- package/dist/optics/defocus-disc-kernel.d.ts +41 -19
- package/dist/optics/defocus-disc-kernel.js +44 -23
- package/dist/optics/defocus-disc-kernel.js.map +1 -1
- package/dist/optics/generic-lens-psf.d.ts +68 -0
- package/dist/optics/generic-lens-psf.js +337 -0
- package/dist/optics/generic-lens-psf.js.map +1 -0
- package/dist/optics/illumination-vignetting.d.ts +6 -0
- package/dist/optics/illumination-vignetting.js +24 -7
- package/dist/optics/illumination-vignetting.js.map +1 -1
- package/dist/optics/sensor-environment-query.d.ts +6 -0
- package/dist/optics/sensor-environment-query.js +13 -6
- package/dist/optics/sensor-environment-query.js.map +1 -1
- package/dist/optics/site-kernel-runs.d.ts +77 -0
- package/dist/optics/site-kernel-runs.js +200 -0
- package/dist/optics/site-kernel-runs.js.map +1 -0
- package/dist/output/generic-lens-corrections.d.ts +24 -0
- package/dist/output/generic-lens-corrections.js +51 -0
- package/dist/output/generic-lens-corrections.js.map +1 -0
- package/docs/AGENT_GUIDE.md +4 -0
- package/docs/API_REFERENCE.md +77 -1
- package/docs/QUICK_START.md +305 -17
- package/docs/USAGE.md +98 -5
- package/docs/api/camera-sensor-equivalence.md +209 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +281 -0
- package/docs/api/equipment-ideal-lens.md +26 -0
- package/docs/api/exports.json +202 -2
- package/docs/api/facade-simulate-capture.md +48 -4
- package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
- package/docs/api/optics-defocus-disc-kernel.md +22 -15
- package/docs/api/optics-sensor-environment-query.md +6 -0
- package/docs/api/output-generic-lens-corrections.md +39 -0
- package/llms.txt +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { EvidenceProvenance } from "../core/evidence-provenance.js";
|
|
2
|
+
import type { LensProjectionModel } from "../camera/lens-projection.js";
|
|
3
|
+
import type { RadialDistortionProfile } from "../optics/radial-distortion.js";
|
|
4
|
+
import type { IlluminationVignettingProfile } from "../optics/illumination-vignetting.js";
|
|
5
|
+
export type GenericLensSet = "professional" | "consumer";
|
|
6
|
+
export type GenericLensKind = "standard" | "macro" | "fisheye";
|
|
7
|
+
/** The character of a set, at each lens's widest aperture. Aberrations are in waves at 550 nm at the frame corner. */
|
|
8
|
+
export interface GenericLensCharacterValues {
|
|
9
|
+
/** Spherical aberration W040 (balanced by W020 = −W040): centre sharpness. */
|
|
10
|
+
sphericalWaves: number;
|
|
11
|
+
/** Coma W131 at H = 1: the comet-shaped point images of night photography. */
|
|
12
|
+
comaWaves: number;
|
|
13
|
+
/** Astigmatism W222 at H = 1: edge and corner sharpness. */
|
|
14
|
+
astigmatismWaves: number;
|
|
15
|
+
/** Field curvature W220 at H = 1: edge and corner sharpness. */
|
|
16
|
+
fieldCurvatureWaves: number;
|
|
17
|
+
/** Axial colour: the F-to-C difference of the defocus W020, waves at 550 nm. */
|
|
18
|
+
axialColorWaves: number;
|
|
19
|
+
/** Lateral colour: the F-to-C difference of the image radius at the corner, as a fraction of the half-diagonal. */
|
|
20
|
+
lateralColorFraction: number;
|
|
21
|
+
/** Corner distortion magnitude |k1| (fraction); each lens gives the sign (barrel −, pincushion +). */
|
|
22
|
+
distortionMagnitude: number;
|
|
23
|
+
/** Corner fall-off in stops that stays when stopped down (natural vignetting). */
|
|
24
|
+
naturalVignettingStops: number;
|
|
25
|
+
/** Extra corner fall-off in stops at the widest aperture (optical vignetting); gone two stops down. */
|
|
26
|
+
opticalVignettingStops: number;
|
|
27
|
+
/** Veiling glare: the fraction of the image's mean irradiance spread as a uniform veil. */
|
|
28
|
+
veilingGlareFraction: number;
|
|
29
|
+
/** Peak ghost power as a fraction of a bright source's power on the axis. */
|
|
30
|
+
ghostFraction: number;
|
|
31
|
+
/** Field-of-view change between infinity and minimum focus as a fraction (negative: narrower), beyond thin-lens unit focusing. */
|
|
32
|
+
breathingFraction: number;
|
|
33
|
+
/** Standard deviation of the autofocus error as a fraction of the depth of focus 2·N·c. */
|
|
34
|
+
focusScatterDepthOfFocusFraction: number;
|
|
35
|
+
/** c in the close-focus factor 1 + c·m/m_max. */
|
|
36
|
+
closeFocusFactor: number;
|
|
37
|
+
}
|
|
38
|
+
/** Per-lens multipliers of the set baseline (missing: 1). `distortion` is signed and required: −1 barrel, +1 pincushion at the baseline. */
|
|
39
|
+
export type GenericLensAdjustments = {
|
|
40
|
+
distortion: number;
|
|
41
|
+
} & Partial<Record<"spherical" | "coma" | "astigmatism" | "fieldCurvature" | "axialColor" | "lateralColor" | "vignetting" | "strayLight" | "breathing" | "focusScatter" | "closeFocus", number>>;
|
|
42
|
+
export interface GenericLensCatalogueEntry {
|
|
43
|
+
/** Catalogue id, for example "pv-50-1.2-pro". */
|
|
44
|
+
lensId: string;
|
|
45
|
+
/** Display name, for example "PV 50 1.2 Pro". */
|
|
46
|
+
name: string;
|
|
47
|
+
set: GenericLensSet;
|
|
48
|
+
kind: GenericLensKind;
|
|
49
|
+
/** Full-frame-equivalent focal length (36 × 24 mm reference). */
|
|
50
|
+
equivalentFocalLengthMm: number;
|
|
51
|
+
widestFNumber: number;
|
|
52
|
+
narrowestFNumber: number;
|
|
53
|
+
/** Largest magnification: 0.15 (the generic rule) or 1 for the professional macro. */
|
|
54
|
+
maximumMagnification: number;
|
|
55
|
+
projection: LensProjectionModel;
|
|
56
|
+
/** Fisheyes only: a circular image inside the frame, or one that fills it. */
|
|
57
|
+
fisheyeImage?: "circular" | "full-frame";
|
|
58
|
+
adjustments: Readonly<GenericLensAdjustments>;
|
|
59
|
+
/** Why this lens departs from its set's baseline. */
|
|
60
|
+
note: string;
|
|
61
|
+
/**
|
|
62
|
+
* A short plain-English summary of the lens's character (at most 40 characters, US spelling), written from its adjustments and
|
|
63
|
+
* `note`, for lists and pickers. It describes only what the catalogue models (bokeh shape is not modeled: the defocus is a disc).
|
|
64
|
+
*/
|
|
65
|
+
summary: string;
|
|
66
|
+
}
|
|
67
|
+
/** The set baselines (owner's table, #549), each lens's starting point before its adjustments. */
|
|
68
|
+
export declare const GENERIC_LENS_SET_BASELINES: Readonly<Record<GenericLensSet, Readonly<GenericLensCharacterValues>>>;
|
|
69
|
+
/** The 30 lenses: 17 professional, then 13 consumer, each set by focal length. */
|
|
70
|
+
export declare const GENERIC_LENS_CATALOGUE: readonly GenericLensCatalogueEntry[];
|
|
71
|
+
/** The catalogue ids, in catalogue order. */
|
|
72
|
+
export declare const GENERIC_LENS_IDS: readonly string[];
|
|
73
|
+
/** Where the catalogue's numbers come from: Photivra's own choices, guided by openly published descriptions of lens behaviour. */
|
|
74
|
+
export declare const GENERIC_LENS_EVIDENCE: readonly EvidenceProvenance[];
|
|
75
|
+
/** Normalised Cauchy dispersion D(λ) = (1/λ² − 1/λ_d²)/(1/λ_F² − 1/λ_C²): 0 at the d line, F minus C = 1. */
|
|
76
|
+
export declare function genericLensDispersion(wavelengthNm: number): number;
|
|
77
|
+
/** A catalogue lens's character resolved for a sensor, an f-number and a focus distance. Aberrations in waves at 550 nm. */
|
|
78
|
+
export interface GenericLensCharacter {
|
|
79
|
+
fNumber: number;
|
|
80
|
+
focusDistanceM: number | null;
|
|
81
|
+
/** Thin-lens magnification at the focus distance (0 at infinity). */
|
|
82
|
+
magnification: number;
|
|
83
|
+
/** 1 + c·m/m_max. */
|
|
84
|
+
closeFocusGain: number;
|
|
85
|
+
/** Pupil-radius ratio N0/N. */
|
|
86
|
+
pupilScale: number;
|
|
87
|
+
/** Resolved Seidel coefficients at the frame corner (H = 1), waves at 550 nm; `balancingDefocusWaves` = −spherical. */
|
|
88
|
+
aberrations: {
|
|
89
|
+
sphericalWaves: number;
|
|
90
|
+
balancingDefocusWaves: number;
|
|
91
|
+
comaWaves: number;
|
|
92
|
+
astigmatismWaves: number;
|
|
93
|
+
fieldCurvatureWaves: number;
|
|
94
|
+
axialColorWaves: number;
|
|
95
|
+
};
|
|
96
|
+
/** Lateral colour: F-to-C image-radius difference at the corner, fraction of the half-diagonal and micrometres on this sensor. */
|
|
97
|
+
lateralColor: {
|
|
98
|
+
fraction: number;
|
|
99
|
+
cornerMicrometers: number;
|
|
100
|
+
};
|
|
101
|
+
/** Radial distortion profile (normalised to the half-diagonal, valid to 1.25 of it, beyond the corner) and its corner value (fraction; negative = barrel). */
|
|
102
|
+
distortion: {
|
|
103
|
+
profile: RadialDistortionProfile;
|
|
104
|
+
corner: number;
|
|
105
|
+
};
|
|
106
|
+
/** Relative illumination profile (normalised to the half-diagonal, valid to 1.25 of it) and the corner fall-off in stops. */
|
|
107
|
+
vignetting: {
|
|
108
|
+
profile: IlluminationVignettingProfile;
|
|
109
|
+
cornerStops: number;
|
|
110
|
+
};
|
|
111
|
+
strayLight: {
|
|
112
|
+
veilingGlareFraction: number;
|
|
113
|
+
ghostFraction: number;
|
|
114
|
+
};
|
|
115
|
+
/** Field-of-view change at this focus and at minimum focus, beyond thin-lens unit focusing (fraction, negative = narrower). */
|
|
116
|
+
breathing: {
|
|
117
|
+
fieldOfViewChange: number;
|
|
118
|
+
atMinimumFocus: number;
|
|
119
|
+
};
|
|
120
|
+
/** Standard deviation of the autofocus error as a fraction of the depth of focus 2·N·c. */
|
|
121
|
+
focusScatterDepthOfFocusFraction: number;
|
|
122
|
+
}
|
|
123
|
+
export interface ResolvedGenericLens {
|
|
124
|
+
lensId: string;
|
|
125
|
+
name: string;
|
|
126
|
+
set: GenericLensSet;
|
|
127
|
+
kind: GenericLensKind;
|
|
128
|
+
equivalentFocalLengthMm: number;
|
|
129
|
+
/** Physical focal length on this sensor: equivalent ÷ crop factor. */
|
|
130
|
+
focalLengthMm: number;
|
|
131
|
+
cropFactor35Mm: number;
|
|
132
|
+
/** Half-diagonal of the sensor, mm: field height H = 1 and ρ = 1 of the profiles. */
|
|
133
|
+
halfDiagonalMm: number;
|
|
134
|
+
widestFNumber: number;
|
|
135
|
+
narrowestFNumber: number;
|
|
136
|
+
/** Marked third-stop f-numbers within the range. */
|
|
137
|
+
fNumbers: readonly number[];
|
|
138
|
+
/** max(0.10 m, f·(1 + 1/m_max)), metres from the lens (thin lens). */
|
|
139
|
+
minimumFocusDistanceM: number;
|
|
140
|
+
maximumMagnification: number;
|
|
141
|
+
projection: LensProjectionModel;
|
|
142
|
+
fisheyeImage: "circular" | "full-frame" | null;
|
|
143
|
+
/** Fisheyes: the radius of the 180° image circle on this sensor, 2·f·sin 45° (equisolid), mm. Null otherwise. */
|
|
144
|
+
imageCircleRadiusMm: number | null;
|
|
145
|
+
character: GenericLensCharacter;
|
|
146
|
+
note: string;
|
|
147
|
+
/** The catalogue entry's `summary`: a short plain-English description of the lens's character (at most 40 characters). */
|
|
148
|
+
summary: string;
|
|
149
|
+
evidence: readonly EvidenceProvenance[];
|
|
150
|
+
scientificStatus: "approximation";
|
|
151
|
+
limitations: readonly string[];
|
|
152
|
+
}
|
|
153
|
+
export interface ResolveGenericLensInput {
|
|
154
|
+
/** A catalogue id (`GENERIC_LENS_IDS`). */
|
|
155
|
+
lensId: string;
|
|
156
|
+
/** The sensor's active capture area, mm. */
|
|
157
|
+
activeImagingArea: {
|
|
158
|
+
widthMm: number;
|
|
159
|
+
heightMm: number;
|
|
160
|
+
};
|
|
161
|
+
/** Default: the widest aperture. Must lie in the lens's range. */
|
|
162
|
+
fNumber?: number;
|
|
163
|
+
/** Default: infinity. At least the minimum focus distance. */
|
|
164
|
+
focusDistanceM?: number;
|
|
165
|
+
}
|
|
166
|
+
/** @internal The catalogue entry of an id, or a domain error naming the ids. */
|
|
167
|
+
export declare function genericLensEntry(lensId: unknown): GenericLensCatalogueEntry;
|
|
168
|
+
/** @internal The baseline with the lens's adjustments applied (at its widest aperture, infinity focus). */
|
|
169
|
+
export declare function genericLensAdjustedValues(lens: GenericLensCatalogueEntry): GenericLensCharacterValues;
|
|
170
|
+
/** @internal E(ρ) = (1 − bρ²)² with E(1) = 2^−stops, valid to `maximumNormalizedRadius` (which must stay inside the first zero). */
|
|
171
|
+
export declare function genericLensVignettingProfile(cornerStops: number, halfDiagonalMm: number, maximumNormalizedRadius?: number): IlluminationVignettingProfile;
|
|
172
|
+
/** @internal r_d = r(1 + k1 ρ²) normalised to the half-diagonal. */
|
|
173
|
+
export declare function genericLensDistortionProfile(corner: number, halfDiagonalMm: number, maximumNormalizedRadius?: number): RadialDistortionProfile;
|
|
174
|
+
/**
|
|
175
|
+
* @internal How far the profiles `resolveGenericLens` returns reach, in units of the half-diagonal. A request that reads the scene
|
|
176
|
+
* through them (`simulateCapture`, `simulateDisplayView`) needs them beyond the frame's corner: the outer site centres plus the
|
|
177
|
+
* kernel radius and the query offsets. 1.25 covers a kernel reaching a quarter of the half-diagonal past the corner (on full
|
|
178
|
+
* frame 5.4 mm, beyond the 512-site disc at 6 µm). Every catalogue lens stays valid there: its vignetting E = (1 − bρ²)² keeps
|
|
179
|
+
* bρ² ≤ 0.83 (b ≤ 0.53, the 2.16-stop corner of PV 16 2.8), and its distortion stays monotonic (1 + 3k1ρ² ≥ 0.75 at k1 = −0.045).
|
|
180
|
+
* Only the operating range grows; the coefficients, and so every value inside the frame, are unchanged.
|
|
181
|
+
*/
|
|
182
|
+
export declare const GENERIC_LENS_RESOLVED_PROFILE_REACH = 1.25;
|
|
183
|
+
/**
|
|
184
|
+
* Resolves a catalogue lens for a sensor: its focal length (equivalent ÷ the diagonal crop factor), f-number range, close focus
|
|
185
|
+
* and projection, and its character (aberrations, colour, distortion, vignetting, flare, breathing and focus scatter) at the
|
|
186
|
+
* given f-number and focus distance. Every value is a labelled approximation of a generic lens, not a measured product.
|
|
187
|
+
*/
|
|
188
|
+
export declare function resolveGenericLens(input: ResolveGenericLensInput): ResolvedGenericLens;
|
|
189
|
+
export interface SimulateGenericLensFocusErrorInput {
|
|
190
|
+
lensId: string;
|
|
191
|
+
activeImagingArea: {
|
|
192
|
+
widthMm: number;
|
|
193
|
+
heightMm: number;
|
|
194
|
+
};
|
|
195
|
+
fNumber: number;
|
|
196
|
+
/** The intended focus distance, m. */
|
|
197
|
+
focusDistanceM: number;
|
|
198
|
+
/** uint32 seed of the shooting session. */
|
|
199
|
+
seed: number;
|
|
200
|
+
/** uint32 index of the shot in the session. Default 0. */
|
|
201
|
+
shotIndex?: number;
|
|
202
|
+
/** Circle of confusion for the depth of focus, mm. Default: the sensor diagonal / 1500. */
|
|
203
|
+
circleOfConfusionMm?: number;
|
|
204
|
+
}
|
|
205
|
+
export interface GenericLensFocusError {
|
|
206
|
+
lensId: string;
|
|
207
|
+
/** The intended and the sampled focus distance, m (the sampled one held within minimum focus and a far limit). */
|
|
208
|
+
intendedFocusDistanceM: number;
|
|
209
|
+
focusDistanceM: number;
|
|
210
|
+
/** The standard normal draw z and the image-side focus error z·σ·2Nc, µm (positive: a longer image distance, so focused nearer). */
|
|
211
|
+
standardNormal: number;
|
|
212
|
+
imageSideErrorMicrometers: number;
|
|
213
|
+
/** σ (fraction of the depth of focus) and the depth of focus 2·N·c, µm. */
|
|
214
|
+
depthOfFocusFraction: number;
|
|
215
|
+
depthOfFocusMicrometers: number;
|
|
216
|
+
/** True when the sampled image distance was held at the minimum-focus or the far (1000 m) limit. */
|
|
217
|
+
held: "minimum-focus" | "far-limit" | null;
|
|
218
|
+
scientificStatus: "approximation";
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Focus consistency (#549): a seeded, reproducible autofocus error for one shot. The image-side error is z·σ·2Nc, with z a
|
|
222
|
+
* standard normal draw from (seed, shotIndex) (MurmurHash3 stream seed, xorshift32 and Box–Muller, the generators of the
|
|
223
|
+
* capture's sensor noise), σ the lens's focus scatter as a fraction of the depth of focus 2·N·c, and
|
|
224
|
+
* c the circle of confusion. The thin-lens image distance of the intended focus moves by that error and the focus distance is
|
|
225
|
+
* read back from it. An approximation of autofocus variation; off unless called (or `simulateCapture`'s `lens.focusScatter`).
|
|
226
|
+
*/
|
|
227
|
+
export declare function simulateGenericLensFocusError(input: SimulateGenericLensFocusErrorInput): GenericLensFocusError;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Generic lens catalogue (#549): two sets of primes, Professional and Consumer, named "PV <focal length> <maximum aperture>"
|
|
4
|
+
* ("Pro" for the professional set, "Macro" or "Fisheye" before it for special lenses). Every lens is Photivra's own generic
|
|
5
|
+
* design, not modelled on any manufacturer's product, and every number in this file is a labelled approximation chosen by
|
|
6
|
+
* Photivra to reproduce the typical behaviour that openly published optics texts describe for each class of lens. No
|
|
7
|
+
* measured, branded or paid lens data is used.
|
|
8
|
+
*
|
|
9
|
+
* The data here are the lens definitions and their character as numbers (a set baseline and per-lens adjustments); the
|
|
10
|
+
* PSF built from them lives in `optics/generic-lens-psf.ts`, so other models (for example bokeh, #553) can read the same
|
|
11
|
+
* data. Each lens's numbers are resolved for a sensor (focal lengths scale by the crop factor as for `lens.prime`), an
|
|
12
|
+
* f-number and a focus distance by {@link resolveGenericLens}.
|
|
13
|
+
*
|
|
14
|
+
* Models (each an approximation):
|
|
15
|
+
* - Primary (Seidel) wave aberrations at the lens's widest aperture and at the frame corner (normalised field H = 1):
|
|
16
|
+
* spherical W040, coma W131, astigmatism W222 and field curvature W220, in waves at 550 nm (Hopkins 1950; Wyant & Creath
|
|
17
|
+
* 1992, Applied Optics and Optical Engineering XI ch. 1, free author copy). Stopping down from N0 to N scales the pupil
|
|
18
|
+
* radius by a = N0/N and so each term W_klm·ρ^l by a^l (a⁴, a³, a², a²). Spherical aberration is balanced by defocus
|
|
19
|
+
* W020 = −W040, the focus of minimum RMS wavefront error (Wyant & Creath 1992, sec. on balanced aberrations).
|
|
20
|
+
* - Close focus: every aberration is multiplied by 1 + c·m/m_max, where m is the thin-lens magnification at the focus
|
|
21
|
+
* distance and c the lens's close-focus factor (a generic stand-in for the change of aberrations with conjugate).
|
|
22
|
+
* - Axial (longitudinal) colour: an extra defocus W020(λ) = W_ax·a²·D(λ), and lateral colour: a radial image displacement
|
|
23
|
+
* Δr(λ) = L·R·H·D(λ) (R the frame's half-diagonal), both with the normalised Cauchy dispersion
|
|
24
|
+
* D(λ) = (1/λ² − 1/λ_d²)/(1/λ_F² − 1/λ_C²) (Fraunhofer d, F and C lines; `calculateCauchyRefractiveIndex`).
|
|
25
|
+
* - Distortion: radial r_d = r·(1 + k1·ρ²), ρ normalised to the frame's half-diagonal, so k1 is the corner distortion.
|
|
26
|
+
* - Vignetting: relative illumination E(ρ) = (1 − b·ρ²)² with E(1) = 2^−s, s the corner fall-off in stops: a natural part,
|
|
27
|
+
* plus an optical part that fades over the first two stops of stopping down.
|
|
28
|
+
* - Flare, focus breathing and focus consistency are numbers for callers; `simulateCapture` renders none of them.
|
|
29
|
+
* @see docs/LENS_CATALOGUE.md for the tables, the adjustments and why each lens has them.
|
|
30
|
+
*/
|
|
31
|
+
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
32
|
+
import { InvalidScientificInputError } from "../core/validation.js";
|
|
33
|
+
import { requireInputKeys, requireInputObject } from "../core/record-validation.js";
|
|
34
|
+
import { calculateEquivalentFocalLength35Mm } from "../camera/equivalent-focal-length.js";
|
|
35
|
+
import { FRAUNHOFER_C_LINE_NM, FRAUNHOFER_D_LINE_NM, FRAUNHOFER_F_LINE_NM } from "../optics/longitudinal-chromatic-aberration.js";
|
|
36
|
+
import { GENERIC_PRIME_MINIMUM_FOCUS, GENERIC_PRIME_THIRD_STOP_F_NUMBERS } from "./generic-prime-lenses.js";
|
|
37
|
+
import { createDeterministicUniformStream, deriveNativeSiteStreamSeedUint32, sampleDeterministicStandardNormal } from "../sensor/stochastic-seed.js";
|
|
38
|
+
/** The set baselines (owner's table, #549), each lens's starting point before its adjustments. */
|
|
39
|
+
export const GENERIC_LENS_SET_BASELINES = /* @__PURE__ */ Object.freeze({
|
|
40
|
+
professional: /* @__PURE__ */ Object.freeze({ sphericalWaves: 0.25, comaWaves: 0.35, astigmatismWaves: 0.3, fieldCurvatureWaves: 0.15, axialColorWaves: 0.15,
|
|
41
|
+
lateralColorFraction: 0.00008, distortionMagnitude: 0.01, naturalVignettingStops: 0.4, opticalVignettingStops: 0.6, veilingGlareFraction: 0.005, ghostFraction: 0.002,
|
|
42
|
+
breathingFraction: -0.03, focusScatterDepthOfFocusFraction: 0.1, closeFocusFactor: 0.15 }),
|
|
43
|
+
consumer: /* @__PURE__ */ Object.freeze({ sphericalWaves: 0.5, comaWaves: 1.0, astigmatismWaves: 0.8, fieldCurvatureWaves: 0.45, axialColorWaves: 0.4,
|
|
44
|
+
lateralColorFraction: 0.0003, distortionMagnitude: 0.03, naturalVignettingStops: 0.7, opticalVignettingStops: 1.1, veilingGlareFraction: 0.02, ghostFraction: 0.008,
|
|
45
|
+
breathingFraction: -0.08, focusScatterDepthOfFocusFraction: 0.3, closeFocusFactor: 0.5 })
|
|
46
|
+
});
|
|
47
|
+
/** The generic smallest aperture of both sets: f/16 up to 16 mm, f/22 up to 135 mm, f/32 beyond (as `lens.prime`). */
|
|
48
|
+
function narrowest(equivalent) { return equivalent <= 16 ? 16 : equivalent <= 135 ? 22 : 32; }
|
|
49
|
+
const PRO = [
|
|
50
|
+
[8, 3.5, "fisheye", { distortion: 0.5, astigmatism: 1.5, fieldCurvature: 1.5, lateralColor: 2, vignetting: 0.8, strayLight: 1.5, breathing: 0.3, closeFocus: 0.5 },
|
|
51
|
+
"Circular fisheye: astigmatism, field curvature and lateral colour grow towards the image circle; the exposed front element flares more; little breathing at 8 mm. Its distortion is the small departure from the equisolid projection.", "Circular fisheye; softer toward the rim", "circular"],
|
|
52
|
+
[10, 3.2, "standard", { distortion: -2.5, astigmatism: 1.4, fieldCurvature: 1.3, lateralColor: 1.8, vignetting: 1.4, strayLight: 1.3 },
|
|
53
|
+
"Rectilinear ultra-wide: strong barrel distortion, softer corners, lateral colour and corner fall-off.", "Strong barrel distortion; softer corners"],
|
|
54
|
+
[15, 2.8, "fisheye", { distortion: 0.5, astigmatism: 1.4, fieldCurvature: 1.3, lateralColor: 1.8, vignetting: 0.9, strayLight: 1.5, breathing: 0.3, closeFocus: 0.5 },
|
|
55
|
+
"Full-frame fisheye: as the circular fisheye, with its image filling the frame.", "Full-frame fisheye; softer edges", "full-frame"],
|
|
56
|
+
[16, 1.8, "standard", { distortion: -1.8, coma: 1.4, astigmatism: 1.3, lateralColor: 1.5, vignetting: 1.5, strayLight: 1.2 },
|
|
57
|
+
"Fast ultra-wide: coma and corner fall-off wide open, barrel distortion.", "Fast ultra-wide; coma wide open"],
|
|
58
|
+
[20, 1.4, "standard", { distortion: -1.3, coma: 1.4, astigmatism: 1.2, lateralColor: 1.3, vignetting: 1.4 }, "Fast wide angle: coma wide open, barrel distortion.", "Fast wide angle; coma wide open"],
|
|
59
|
+
[24, 1.4, "standard", { distortion: -1, coma: 1.3, astigmatism: 1.1, lateralColor: 1.2, vignetting: 1.3 }, "Fast wide angle: some coma wide open.", "Fast wide angle; some coma wide open"],
|
|
60
|
+
[35, 1.2, "standard", { distortion: -0.6, spherical: 1.3, coma: 1.2, axialColor: 1.3, vignetting: 1.3 }, "Very fast: spherical aberration and axial colour wide open.", "Very fast; axial color wide open"],
|
|
61
|
+
[50, 1.2, "standard", { distortion: -0.3, spherical: 1.3, axialColor: 1.3, vignetting: 1.2 }, "Very fast normal lens: spherical aberration and axial colour wide open; nearly no distortion.", "Very fast; nearly no distortion"],
|
|
62
|
+
[85, 1.2, "standard", { distortion: 0.3, spherical: 1.2, axialColor: 1.4, coma: 0.8, astigmatism: 0.7, vignetting: 1.1 },
|
|
63
|
+
"Very fast short telephoto: axial colour wide open, a small field angle so less coma and astigmatism; slight pincushion.", "Even across the frame; axial color"],
|
|
64
|
+
[100, 2.8, "macro", { distortion: 0.1, spherical: 0.8, coma: 0.7, astigmatism: 0.7, fieldCurvature: 0.5, axialColor: 0.8, vignetting: 0.6, breathing: 1.5, closeFocus: 0.2 },
|
|
65
|
+
"Macro: a flat field and corrections optimised for close focus (to 1:1), but strong breathing.", "Flat field to 1:1; strong breathing"],
|
|
66
|
+
[200, 2, "standard", { distortion: 0.4, axialColor: 1.5, lateralColor: 0.8, coma: 0.6, astigmatism: 0.6, vignetting: 0.9 }, "Fast long telephoto: axial colour; a small field angle.", "Fast telephoto; axial color wide open"],
|
|
67
|
+
[300, 2.8, "standard", { distortion: 0.4, axialColor: 1.4, coma: 0.5, astigmatism: 0.5, vignetting: 0.8 }, "Long telephoto: axial colour; a small field angle.", "Long telephoto; axial color"],
|
|
68
|
+
[400, 2.8, "standard", { distortion: 0.4, axialColor: 1.5, coma: 0.5, astigmatism: 0.5, vignetting: 0.9 }, "Fast super telephoto: axial colour.", "Fast super telephoto; axial color"],
|
|
69
|
+
[500, 4, "standard", { distortion: 0.3, axialColor: 1.2, coma: 0.5, astigmatism: 0.5, vignetting: 0.7 }, "Super telephoto.", "Super telephoto; low coma"],
|
|
70
|
+
[600, 4, "standard", { distortion: 0.3, axialColor: 1.3, coma: 0.5, astigmatism: 0.5, vignetting: 0.8 }, "Super telephoto.", "Super telephoto; low coma, axial color"],
|
|
71
|
+
[800, 5.6, "standard", { distortion: 0.3, axialColor: 1.2, coma: 0.5, astigmatism: 0.5, vignetting: 0.7 }, "Super telephoto.", "Slow super telephoto; low coma"],
|
|
72
|
+
[1200, 8, "standard", { distortion: 0.2, axialColor: 1.1, coma: 0.5, astigmatism: 0.5, vignetting: 0.6 }, "Slow extreme telephoto: little vignetting.", "Slow extreme telephoto; little falloff"]
|
|
73
|
+
];
|
|
74
|
+
const CONSUMER = [
|
|
75
|
+
[16, 2.8, "standard", { distortion: -1.5, coma: 1.1, astigmatism: 1.2, fieldCurvature: 1.2, lateralColor: 1.3, vignetting: 1.2, strayLight: 1.2 },
|
|
76
|
+
"Ultra-wide: strong barrel distortion and soft corners, left to the correction profile.", "Strong barrel distortion; soft corners"],
|
|
77
|
+
[20, 2, "standard", { distortion: -1.2, coma: 1.15, astigmatism: 1.1, lateralColor: 1.2, vignetting: 1.15 }, "Wide angle: barrel distortion, coma wide open.", "Barrel distortion; coma wide open"],
|
|
78
|
+
[24, 1.8, "standard", { distortion: -1, coma: 1.1, lateralColor: 1.1, vignetting: 1.1 }, "Wide angle: barrel distortion.", "Barrel distortion; mild corner falloff"],
|
|
79
|
+
[35, 1.8, "standard", { distortion: -0.6 }, "The consumer baseline.", "The consumer baseline; mild barrel"],
|
|
80
|
+
[50, 1.8, "standard", { distortion: -0.3, spherical: 0.7, coma: 0.7, astigmatism: 0.7, fieldCurvature: 0.7, axialColor: 0.8, lateralColor: 0.6, vignetting: 0.8 },
|
|
81
|
+
"A simple, well-corrected normal design: quite good for its set (owner's example).", "Simple, well-corrected normal lens"],
|
|
82
|
+
[85, 1.8, "standard", { distortion: 0.3, spherical: 0.9, axialColor: 1.1, coma: 0.8, astigmatism: 0.8 }, "Short telephoto: axial colour wide open.", "Short telephoto; axial color wide open"],
|
|
83
|
+
[100, 2, "standard", { distortion: 0.4, axialColor: 1.2, coma: 0.85 }, "Short telephoto: axial colour wide open.", "Short telephoto; axial color, pincushion"],
|
|
84
|
+
[200, 5.6, "standard", { distortion: 0.6, axialColor: 0.9, lateralColor: 1.2, coma: 0.8, astigmatism: 0.9, vignetting: 0.8 }, "Slow telephoto: lateral colour, pincushion.", "Lateral color and pincushion"],
|
|
85
|
+
[300, 5.6, "standard", { distortion: 0.6, lateralColor: 1.3 }, "Slow telephoto: lateral colour, pincushion.", "Lateral color and pincushion"],
|
|
86
|
+
[400, 5.6, "standard", { distortion: 0.6, lateralColor: 1.3, axialColor: 1.1 }, "Slow super telephoto: lateral and axial colour.", "Slow super telephoto; lateral color"],
|
|
87
|
+
[500, 5.6, "standard", { distortion: 0.6, lateralColor: 1.4, axialColor: 1.15, astigmatism: 1.1, fieldCurvature: 1.1 }, "Slow super telephoto: softer edges.", "Slow super telephoto; softer edges"],
|
|
88
|
+
[600, 6.3, "standard", { distortion: 0.6, spherical: 1.2, coma: 1.2, astigmatism: 1.25, fieldCurvature: 1.25, lateralColor: 1.4, axialColor: 1.2, vignetting: 1.2 },
|
|
89
|
+
"Soft at the edges and wide open (owner's example).", "Soft at the edges and wide open"],
|
|
90
|
+
[800, 9, "standard", { distortion: 0.5, astigmatism: 1.15, fieldCurvature: 1.15, lateralColor: 1.4, axialColor: 1.1 }, "Slow extreme telephoto: softer edges, lateral colour.", "Slow extreme telephoto; softer edges"]
|
|
91
|
+
];
|
|
92
|
+
function entry(set, [focal, widest, kind, adjustments, note, summary, fisheye]) {
|
|
93
|
+
const special = kind === "macro" ? " Macro" : kind === "fisheye" ? " Fisheye" : "";
|
|
94
|
+
const suffix = set === "professional" ? " Pro" : "";
|
|
95
|
+
return Object.freeze({ lensId: ("pv-" + focal + "-" + widest + special + suffix).toLowerCase().replace(/ /g, "-"), name: "PV " + focal + " " + widest + special + suffix,
|
|
96
|
+
set, kind, equivalentFocalLengthMm: focal, widestFNumber: widest, narrowestFNumber: narrowest(focal),
|
|
97
|
+
maximumMagnification: kind === "macro" ? 1 : GENERIC_PRIME_MINIMUM_FOCUS.magnification, projection: kind === "fisheye" ? "fisheye-equisolid-angle" : "rectilinear",
|
|
98
|
+
...(fisheye ? { fisheyeImage: fisheye } : {}), adjustments: Object.freeze({ ...adjustments }), note, summary });
|
|
99
|
+
}
|
|
100
|
+
/** The 30 lenses: 17 professional, then 13 consumer, each set by focal length. */
|
|
101
|
+
export const GENERIC_LENS_CATALOGUE = /* @__PURE__ */ Object.freeze([...PRO.map((row) => entry("professional", row)),
|
|
102
|
+
...CONSUMER.map((row) => entry("consumer", row))]);
|
|
103
|
+
/** The catalogue ids, in catalogue order. */
|
|
104
|
+
export const GENERIC_LENS_IDS = /* @__PURE__ */ Object.freeze(GENERIC_LENS_CATALOGUE.map((lens) => lens.lensId));
|
|
105
|
+
/** Where the catalogue's numbers come from: Photivra's own choices, guided by openly published descriptions of lens behaviour. */
|
|
106
|
+
export const GENERIC_LENS_EVIDENCE = /* @__PURE__ */ Object.freeze([
|
|
107
|
+
/* @__PURE__ */ Object.freeze({ sourceOrigin: "photivra", sourceReference: "photivra:generic-lens-catalogue:549", reuseStatus: "photivra-owned",
|
|
108
|
+
note: "Coefficients chosen by Photivra (#549); not measured, not modelled on any product." }),
|
|
109
|
+
/* @__PURE__ */ Object.freeze({ sourceOrigin: "third-party", sourceReference: "Wyant & Creath 1992, Basic Wavefront Aberration Theory for Optical Metrology, Applied Optics and Optical Engineering XI ch. 1 (free author copy)",
|
|
110
|
+
reuseStatus: "factual-reference-only", note: "Seidel wave-aberration expansion, pupil scaling and balanced spherical aberration; no data reproduced." }),
|
|
111
|
+
/* @__PURE__ */ Object.freeze({ sourceOrigin: "third-party", sourceReference: "Wikipedia: Vignetting; Distortion (optics); Chromatic aberration; Coma (optics); Lens flare; Focus breathing",
|
|
112
|
+
reuseStatus: "factual-reference-only", note: "Qualitative descriptions of how these effects vary with lens class and aperture; no data reproduced." })
|
|
113
|
+
]);
|
|
114
|
+
/** Normalised Cauchy dispersion D(λ) = (1/λ² − 1/λ_d²)/(1/λ_F² − 1/λ_C²): 0 at the d line, F minus C = 1. */
|
|
115
|
+
export function genericLensDispersion(wavelengthNm) {
|
|
116
|
+
const inv = (nm) => 1 / (nm * nm);
|
|
117
|
+
return (inv(wavelengthNm) - inv(FRAUNHOFER_D_LINE_NM)) / (inv(FRAUNHOFER_F_LINE_NM) - inv(FRAUNHOFER_C_LINE_NM));
|
|
118
|
+
}
|
|
119
|
+
/** @internal The catalogue entry of an id, or a domain error naming the ids. */
|
|
120
|
+
export function genericLensEntry(lensId) {
|
|
121
|
+
const found = typeof lensId === "string" ? GENERIC_LENS_CATALOGUE.find((lens) => lens.lensId === lensId) : undefined;
|
|
122
|
+
if (!found)
|
|
123
|
+
throw new InvalidConfigurationError("lensId must be a generic lens catalogue id (GENERIC_LENS_IDS), for example \"pv-50-1.2-pro\" or \"pv-50-1.8\".");
|
|
124
|
+
return found;
|
|
125
|
+
}
|
|
126
|
+
/** @internal The baseline with the lens's adjustments applied (at its widest aperture, infinity focus). */
|
|
127
|
+
export function genericLensAdjustedValues(lens) {
|
|
128
|
+
const b = GENERIC_LENS_SET_BASELINES[lens.set], a = lens.adjustments, k = (key) => a[key] ?? 1;
|
|
129
|
+
return { sphericalWaves: b.sphericalWaves * k("spherical"), comaWaves: b.comaWaves * k("coma"), astigmatismWaves: b.astigmatismWaves * k("astigmatism"),
|
|
130
|
+
fieldCurvatureWaves: b.fieldCurvatureWaves * k("fieldCurvature"), axialColorWaves: b.axialColorWaves * k("axialColor"),
|
|
131
|
+
lateralColorFraction: b.lateralColorFraction * k("lateralColor"), distortionMagnitude: b.distortionMagnitude * a.distortion,
|
|
132
|
+
naturalVignettingStops: b.naturalVignettingStops * k("vignetting"), opticalVignettingStops: b.opticalVignettingStops * k("vignetting"),
|
|
133
|
+
veilingGlareFraction: b.veilingGlareFraction * k("strayLight"), ghostFraction: b.ghostFraction * k("strayLight"), breathingFraction: b.breathingFraction * k("breathing"),
|
|
134
|
+
focusScatterDepthOfFocusFraction: b.focusScatterDepthOfFocusFraction * k("focusScatter"), closeFocusFactor: b.closeFocusFactor * k("closeFocus") };
|
|
135
|
+
}
|
|
136
|
+
/** @internal E(ρ) = (1 − bρ²)² with E(1) = 2^−stops, valid to `maximumNormalizedRadius` (which must stay inside the first zero). */
|
|
137
|
+
export function genericLensVignettingProfile(cornerStops, halfDiagonalMm, maximumNormalizedRadius = 1) {
|
|
138
|
+
const b = 1 - 2 ** (-cornerStops / 2);
|
|
139
|
+
return { normalizationRadiusMm: halfDiagonalMm, maximumNormalizedRadius, coefficients: { r2: -2 * b, r4: b * b, r6: 0 } };
|
|
140
|
+
}
|
|
141
|
+
/** @internal r_d = r(1 + k1 ρ²) normalised to the half-diagonal. */
|
|
142
|
+
export function genericLensDistortionProfile(corner, halfDiagonalMm, maximumNormalizedRadius = 1) {
|
|
143
|
+
return { normalizationRadiusMm: halfDiagonalMm, maximumNormalizedRadius, coefficients: { k1: corner, k2: 0, k3: 0 } };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @internal How far the profiles `resolveGenericLens` returns reach, in units of the half-diagonal. A request that reads the scene
|
|
147
|
+
* through them (`simulateCapture`, `simulateDisplayView`) needs them beyond the frame's corner: the outer site centres plus the
|
|
148
|
+
* kernel radius and the query offsets. 1.25 covers a kernel reaching a quarter of the half-diagonal past the corner (on full
|
|
149
|
+
* frame 5.4 mm, beyond the 512-site disc at 6 µm). Every catalogue lens stays valid there: its vignetting E = (1 − bρ²)² keeps
|
|
150
|
+
* bρ² ≤ 0.83 (b ≤ 0.53, the 2.16-stop corner of PV 16 2.8), and its distortion stays monotonic (1 + 3k1ρ² ≥ 0.75 at k1 = −0.045).
|
|
151
|
+
* Only the operating range grows; the coefficients, and so every value inside the frame, are unchanged.
|
|
152
|
+
*/
|
|
153
|
+
export const GENERIC_LENS_RESOLVED_PROFILE_REACH = 1.25;
|
|
154
|
+
/** The distortion profile's range for that reach: with barrel distortion an image point at ρ_d comes from ρ_d/(1 + k1ρ²), farther out. */
|
|
155
|
+
function resolvedDistortionReach(k1) {
|
|
156
|
+
const r = GENERIC_LENS_RESOLVED_PROFILE_REACH;
|
|
157
|
+
return k1 < 0 ? r / (1 + k1 * r * r) * 1.01 : r;
|
|
158
|
+
}
|
|
159
|
+
function positiveFinite(value, name) {
|
|
160
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
|
|
161
|
+
throw new InvalidScientificInputError(name + " must be a positive finite number.");
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Resolves a catalogue lens for a sensor: its focal length (equivalent ÷ the diagonal crop factor), f-number range, close focus
|
|
166
|
+
* and projection, and its character (aberrations, colour, distortion, vignetting, flare, breathing and focus scatter) at the
|
|
167
|
+
* given f-number and focus distance. Every value is a labelled approximation of a generic lens, not a measured product.
|
|
168
|
+
*/
|
|
169
|
+
export function resolveGenericLens(input) {
|
|
170
|
+
requireInputObject(input, "resolveGenericLens input");
|
|
171
|
+
requireInputKeys(input, "resolveGenericLens", ["lensId", "activeImagingArea", "fNumber", "focusDistanceM"]);
|
|
172
|
+
const lens = genericLensEntry(input.lensId);
|
|
173
|
+
requireInputObject(input.activeImagingArea, "activeImagingArea");
|
|
174
|
+
const widthMm = positiveFinite(input.activeImagingArea.widthMm, "activeImagingArea.widthMm"), heightMm = positiveFinite(input.activeImagingArea.heightMm, "activeImagingArea.heightMm");
|
|
175
|
+
const crop = calculateEquivalentFocalLength35Mm({ focalLengthMm: 1, activeImagingArea: { widthMm, heightMm } }).value.cropFactor35Mm;
|
|
176
|
+
const focalLengthMm = lens.equivalentFocalLengthMm / crop, f = focalLengthMm / 1000, halfDiagonalMm = Math.hypot(widthMm, heightMm) / 2;
|
|
177
|
+
const minimumFocusDistanceM = Math.max(GENERIC_PRIME_MINIMUM_FOCUS.minimumDistanceM, f * (1 + 1 / lens.maximumMagnification));
|
|
178
|
+
const fNumber = input.fNumber === undefined ? lens.widestFNumber : positiveFinite(input.fNumber, "fNumber");
|
|
179
|
+
if (fNumber < lens.widestFNumber || fNumber > lens.narrowestFNumber)
|
|
180
|
+
throw new InvalidScientificInputError("fNumber " + fNumber + " is outside the " + lens.name + " range f/" + lens.widestFNumber + "–f/" + lens.narrowestFNumber + ".");
|
|
181
|
+
const focusDistanceM = input.focusDistanceM === undefined ? null : positiveFinite(input.focusDistanceM, "focusDistanceM");
|
|
182
|
+
if (focusDistanceM !== null && focusDistanceM < minimumFocusDistanceM)
|
|
183
|
+
throw new InvalidScientificInputError("focusDistanceM " + focusDistanceM + " m is closer than the " + lens.name + " minimum focus distance " + minimumFocusDistanceM + " m.");
|
|
184
|
+
const values = genericLensAdjustedValues(lens), maximumMagnification = f / (minimumFocusDistanceM - f);
|
|
185
|
+
const magnification = focusDistanceM === null ? 0 : f / (focusDistanceM - f), closeness = Math.min(1, magnification / maximumMagnification);
|
|
186
|
+
const closeFocusGain = 1 + values.closeFocusFactor * closeness, a = lens.widestFNumber / fNumber;
|
|
187
|
+
const cornerStops = values.naturalVignettingStops + values.opticalVignettingStops * Math.min(1, Math.max(0, 1 - Math.log2(fNumber / lens.widestFNumber) / 2));
|
|
188
|
+
const character = { fNumber, focusDistanceM, magnification, closeFocusGain, pupilScale: a,
|
|
189
|
+
aberrations: { sphericalWaves: values.sphericalWaves * a ** 4 * closeFocusGain, balancingDefocusWaves: -values.sphericalWaves * a ** 4 * closeFocusGain,
|
|
190
|
+
comaWaves: values.comaWaves * a ** 3 * closeFocusGain, astigmatismWaves: values.astigmatismWaves * a * a * closeFocusGain,
|
|
191
|
+
fieldCurvatureWaves: values.fieldCurvatureWaves * a * a * closeFocusGain, axialColorWaves: values.axialColorWaves * a * a * closeFocusGain },
|
|
192
|
+
lateralColor: { fraction: values.lateralColorFraction, cornerMicrometers: values.lateralColorFraction * halfDiagonalMm * 1000 },
|
|
193
|
+
distortion: { profile: genericLensDistortionProfile(values.distortionMagnitude, halfDiagonalMm, resolvedDistortionReach(values.distortionMagnitude)), corner: values.distortionMagnitude },
|
|
194
|
+
vignetting: { profile: genericLensVignettingProfile(cornerStops, halfDiagonalMm, GENERIC_LENS_RESOLVED_PROFILE_REACH), cornerStops },
|
|
195
|
+
strayLight: { veilingGlareFraction: values.veilingGlareFraction, ghostFraction: values.ghostFraction },
|
|
196
|
+
breathing: { fieldOfViewChange: values.breathingFraction * closeness, atMinimumFocus: values.breathingFraction },
|
|
197
|
+
focusScatterDepthOfFocusFraction: values.focusScatterDepthOfFocusFraction };
|
|
198
|
+
const fisheye = lens.kind === "fisheye";
|
|
199
|
+
return Object.freeze({ lensId: lens.lensId, name: lens.name, set: lens.set, kind: lens.kind, equivalentFocalLengthMm: lens.equivalentFocalLengthMm, focalLengthMm,
|
|
200
|
+
cropFactor35Mm: crop, halfDiagonalMm, widestFNumber: lens.widestFNumber, narrowestFNumber: lens.narrowestFNumber,
|
|
201
|
+
fNumbers: Object.freeze(GENERIC_PRIME_THIRD_STOP_F_NUMBERS.filter((n) => n >= lens.widestFNumber && n <= lens.narrowestFNumber)),
|
|
202
|
+
minimumFocusDistanceM, maximumMagnification, projection: lens.projection, fisheyeImage: lens.fisheyeImage ?? null,
|
|
203
|
+
imageCircleRadiusMm: fisheye ? 2 * focalLengthMm * Math.SQRT1_2 : null, character, note: lens.note, summary: lens.summary, evidence: GENERIC_LENS_EVIDENCE,
|
|
204
|
+
scientificStatus: "approximation", limitations: Object.freeze([
|
|
205
|
+
"A generic Photivra lens, not a real product: every coefficient is a Photivra choice that reproduces typical behaviour of its class, not a measurement.",
|
|
206
|
+
"Primary (Seidel) aberrations only, scaled with the pupil radius and a generic close-focus factor; no higher-order terms, no zoom or focus-group design.",
|
|
207
|
+
"Focal length, close focus and magnification are thin-lens values; the lens design is scaled to the sensor by the crop factor with its wavefront error in waves kept.",
|
|
208
|
+
"Flare, focus breathing and focus scatter are reported numbers; simulateCapture renders none of them."
|
|
209
|
+
]) });
|
|
210
|
+
}
|
|
211
|
+
/** Stream id of the focus-scatter draw ("FOCS"), kept apart from the sensor noise streams 0 and 1. */
|
|
212
|
+
const FOCUS_SCATTER_STREAM_ID = 0x464f4353;
|
|
213
|
+
/** The far limit of a sampled focus distance, m. */
|
|
214
|
+
const FOCUS_SCATTER_FAR_LIMIT_M = 1000;
|
|
215
|
+
function uint32(value, name) {
|
|
216
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 0xffffffff)
|
|
217
|
+
throw new InvalidScientificInputError(name + " must be a uint32 integer.");
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Focus consistency (#549): a seeded, reproducible autofocus error for one shot. The image-side error is z·σ·2Nc, with z a
|
|
222
|
+
* standard normal draw from (seed, shotIndex) (MurmurHash3 stream seed, xorshift32 and Box–Muller, the generators of the
|
|
223
|
+
* capture's sensor noise), σ the lens's focus scatter as a fraction of the depth of focus 2·N·c, and
|
|
224
|
+
* c the circle of confusion. The thin-lens image distance of the intended focus moves by that error and the focus distance is
|
|
225
|
+
* read back from it. An approximation of autofocus variation; off unless called (or `simulateCapture`'s `lens.focusScatter`).
|
|
226
|
+
*/
|
|
227
|
+
export function simulateGenericLensFocusError(input) {
|
|
228
|
+
requireInputObject(input, "simulateGenericLensFocusError input");
|
|
229
|
+
requireInputKeys(input, "simulateGenericLensFocusError", ["lensId", "activeImagingArea", "fNumber", "focusDistanceM", "seed", "shotIndex", "circleOfConfusionMm"]);
|
|
230
|
+
const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea, fNumber: input.fNumber, focusDistanceM: input.focusDistanceM });
|
|
231
|
+
const seed = uint32(input.seed, "seed"), shot = input.shotIndex === undefined ? 0 : uint32(input.shotIndex, "shotIndex");
|
|
232
|
+
const c = input.circleOfConfusionMm === undefined ? 2 * lens.halfDiagonalMm / 1500 : positiveFinite(input.circleOfConfusionMm, "circleOfConfusionMm");
|
|
233
|
+
const z = sampleDeterministicStandardNormal(createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, shot, FOCUS_SCATTER_STREAM_ID)));
|
|
234
|
+
const sigma = lens.character.focusScatterDepthOfFocusFraction, depthOfFocusMm = 2 * lens.character.fNumber * c, errorMm = z * sigma * depthOfFocusMm;
|
|
235
|
+
const f = lens.focalLengthMm, imageDistance = (d) => 1 / (1 / f - 1 / (d * 1000));
|
|
236
|
+
const nearest = imageDistance(lens.minimumFocusDistanceM), farthest = imageDistance(FOCUS_SCATTER_FAR_LIMIT_M);
|
|
237
|
+
const v = imageDistance(input.focusDistanceM) + errorMm, held = v > nearest ? "minimum-focus" : v < farthest ? "far-limit" : null;
|
|
238
|
+
const vHeld = Math.min(nearest, Math.max(farthest, v));
|
|
239
|
+
const focusDistanceM = held === "minimum-focus" ? lens.minimumFocusDistanceM : held === "far-limit" ? FOCUS_SCATTER_FAR_LIMIT_M : 1 / (1 / f - 1 / vHeld) / 1000;
|
|
240
|
+
return Object.freeze({ lensId: lens.lensId, intendedFocusDistanceM: input.focusDistanceM, focusDistanceM, standardNormal: z, imageSideErrorMicrometers: errorMm * 1000,
|
|
241
|
+
depthOfFocusFraction: sigma, depthOfFocusMicrometers: depthOfFocusMm * 1000, held, scientificStatus: "approximation" });
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=generic-lens-catalogue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-lens-catalogue.js","sourceRoot":"","sources":["../../src/equipment/generic-lens-catalogue.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic lens catalogue (#549): two sets of primes, Professional and Consumer, named \"PV <focal length> <maximum aperture>\"\n * (\"Pro\" for the professional set, \"Macro\" or \"Fisheye\" before it for special lenses). Every lens is Photivra's own generic\n * design, not modelled on any manufacturer's product, and every number in this file is a labelled approximation chosen by\n * Photivra to reproduce the typical behaviour that openly published optics texts describe for each class of lens. No\n * measured, branded or paid lens data is used.\n *\n * The data here are the lens definitions and their character as numbers (a set baseline and per-lens adjustments); the\n * PSF built from them lives in `optics/generic-lens-psf.ts`, so other models (for example bokeh, #553) can read the same\n * data. Each lens's numbers are resolved for a sensor (focal lengths scale by the crop factor as for `lens.prime`), an\n * f-number and a focus distance by {@link resolveGenericLens}.\n *\n * Models (each an approximation):\n * - Primary (Seidel) wave aberrations at the lens's widest aperture and at the frame corner (normalised field H = 1):\n * spherical W040, coma W131, astigmatism W222 and field curvature W220, in waves at 550 nm (Hopkins 1950; Wyant & Creath\n * 1992, Applied Optics and Optical Engineering XI ch. 1, free author copy). Stopping down from N0 to N scales the pupil\n * radius by a = N0/N and so each term W_klm·ρ^l by a^l (a⁴, a³, a², a²). Spherical aberration is balanced by defocus\n * W020 = −W040, the focus of minimum RMS wavefront error (Wyant & Creath 1992, sec. on balanced aberrations).\n * - Close focus: every aberration is multiplied by 1 + c·m/m_max, where m is the thin-lens magnification at the focus\n * distance and c the lens's close-focus factor (a generic stand-in for the change of aberrations with conjugate).\n * - Axial (longitudinal) colour: an extra defocus W020(λ) = W_ax·a²·D(λ), and lateral colour: a radial image displacement\n * Δr(λ) = L·R·H·D(λ) (R the frame's half-diagonal), both with the normalised Cauchy dispersion\n * D(λ) = (1/λ² − 1/λ_d²)/(1/λ_F² − 1/λ_C²) (Fraunhofer d, F and C lines; `calculateCauchyRefractiveIndex`).\n * - Distortion: radial r_d = r·(1 + k1·ρ²), ρ normalised to the frame's half-diagonal, so k1 is the corner distortion.\n * - Vignetting: relative illumination E(ρ) = (1 − b·ρ²)² with E(1) = 2^−s, s the corner fall-off in stops: a natural part,\n * plus an optical part that fades over the first two stops of stopping down.\n * - Flare, focus breathing and focus consistency are numbers for callers; `simulateCapture` renders none of them.\n * @see docs/LENS_CATALOGUE.md for the tables, the adjustments and why each lens has them.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport type { EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { calculateEquivalentFocalLength35Mm } from \"../camera/equivalent-focal-length.js\";\nimport type { LensProjectionModel } from \"../camera/lens-projection.js\";\nimport { FRAUNHOFER_C_LINE_NM, FRAUNHOFER_D_LINE_NM, FRAUNHOFER_F_LINE_NM } from \"../optics/longitudinal-chromatic-aberration.js\";\nimport type { RadialDistortionProfile } from \"../optics/radial-distortion.js\";\nimport type { IlluminationVignettingProfile } from \"../optics/illumination-vignetting.js\";\nimport { GENERIC_PRIME_MINIMUM_FOCUS, GENERIC_PRIME_THIRD_STOP_F_NUMBERS } from \"./generic-prime-lenses.js\";\nimport { createDeterministicUniformStream, deriveNativeSiteStreamSeedUint32, sampleDeterministicStandardNormal } from \"../sensor/stochastic-seed.js\";\n\nexport type GenericLensSet = \"professional\" | \"consumer\";\nexport type GenericLensKind = \"standard\" | \"macro\" | \"fisheye\";\n\n/** The character of a set, at each lens's widest aperture. Aberrations are in waves at 550 nm at the frame corner. */\nexport interface GenericLensCharacterValues {\n /** Spherical aberration W040 (balanced by W020 = −W040): centre sharpness. */\n sphericalWaves: number;\n /** Coma W131 at H = 1: the comet-shaped point images of night photography. */\n comaWaves: number;\n /** Astigmatism W222 at H = 1: edge and corner sharpness. */\n astigmatismWaves: number;\n /** Field curvature W220 at H = 1: edge and corner sharpness. */\n fieldCurvatureWaves: number;\n /** Axial colour: the F-to-C difference of the defocus W020, waves at 550 nm. */\n axialColorWaves: number;\n /** Lateral colour: the F-to-C difference of the image radius at the corner, as a fraction of the half-diagonal. */\n lateralColorFraction: number;\n /** Corner distortion magnitude |k1| (fraction); each lens gives the sign (barrel −, pincushion +). */\n distortionMagnitude: number;\n /** Corner fall-off in stops that stays when stopped down (natural vignetting). */\n naturalVignettingStops: number;\n /** Extra corner fall-off in stops at the widest aperture (optical vignetting); gone two stops down. */\n opticalVignettingStops: number;\n /** Veiling glare: the fraction of the image's mean irradiance spread as a uniform veil. */\n veilingGlareFraction: number;\n /** Peak ghost power as a fraction of a bright source's power on the axis. */\n ghostFraction: number;\n /** Field-of-view change between infinity and minimum focus as a fraction (negative: narrower), beyond thin-lens unit focusing. */\n breathingFraction: number;\n /** Standard deviation of the autofocus error as a fraction of the depth of focus 2·N·c. */\n focusScatterDepthOfFocusFraction: number;\n /** c in the close-focus factor 1 + c·m/m_max. */\n closeFocusFactor: number;\n}\n\n/** Per-lens multipliers of the set baseline (missing: 1). `distortion` is signed and required: −1 barrel, +1 pincushion at the baseline. */\nexport type GenericLensAdjustments = { distortion: number } & Partial<Record<\"spherical\" | \"coma\" | \"astigmatism\" | \"fieldCurvature\" | \"axialColor\" | \"lateralColor\" |\n \"vignetting\" | \"strayLight\" | \"breathing\" | \"focusScatter\" | \"closeFocus\", number>>;\n\nexport interface GenericLensCatalogueEntry {\n /** Catalogue id, for example \"pv-50-1.2-pro\". */\n lensId: string;\n /** Display name, for example \"PV 50 1.2 Pro\". */\n name: string;\n set: GenericLensSet;\n kind: GenericLensKind;\n /** Full-frame-equivalent focal length (36 × 24 mm reference). */\n equivalentFocalLengthMm: number;\n widestFNumber: number;\n narrowestFNumber: number;\n /** Largest magnification: 0.15 (the generic rule) or 1 for the professional macro. */\n maximumMagnification: number;\n projection: LensProjectionModel;\n /** Fisheyes only: a circular image inside the frame, or one that fills it. */\n fisheyeImage?: \"circular\" | \"full-frame\";\n adjustments: Readonly<GenericLensAdjustments>;\n /** Why this lens departs from its set's baseline. */\n note: string;\n /**\n * A short plain-English summary of the lens's character (at most 40 characters, US spelling), written from its adjustments and\n * `note`, for lists and pickers. It describes only what the catalogue models (bokeh shape is not modeled: the defocus is a disc).\n */\n summary: string;\n}\n\n/** The set baselines (owner's table, #549), each lens's starting point before its adjustments. */\nexport const GENERIC_LENS_SET_BASELINES: Readonly<Record<GenericLensSet, Readonly<GenericLensCharacterValues>>> = /* @__PURE__ */ Object.freeze({\n professional: /* @__PURE__ */ Object.freeze({ sphericalWaves: 0.25, comaWaves: 0.35, astigmatismWaves: 0.3, fieldCurvatureWaves: 0.15, axialColorWaves: 0.15,\n lateralColorFraction: 0.00008, distortionMagnitude: 0.01, naturalVignettingStops: 0.4, opticalVignettingStops: 0.6, veilingGlareFraction: 0.005, ghostFraction: 0.002,\n breathingFraction: -0.03, focusScatterDepthOfFocusFraction: 0.1, closeFocusFactor: 0.15 }),\n consumer: /* @__PURE__ */ Object.freeze({ sphericalWaves: 0.5, comaWaves: 1.0, astigmatismWaves: 0.8, fieldCurvatureWaves: 0.45, axialColorWaves: 0.4,\n lateralColorFraction: 0.0003, distortionMagnitude: 0.03, naturalVignettingStops: 0.7, opticalVignettingStops: 1.1, veilingGlareFraction: 0.02, ghostFraction: 0.008,\n breathingFraction: -0.08, focusScatterDepthOfFocusFraction: 0.3, closeFocusFactor: 0.5 })\n});\n\n/** The generic smallest aperture of both sets: f/16 up to 16 mm, f/22 up to 135 mm, f/32 beyond (as `lens.prime`). */\nfunction narrowest(equivalent: number): number { return equivalent <= 16 ? 16 : equivalent <= 135 ? 22 : 32; }\n\ntype Row = [focal: number, widest: number, kind: GenericLensKind, adjustments: GenericLensAdjustments, note: string, summary: string, fisheye?: \"circular\" | \"full-frame\"];\nconst PRO: readonly Row[] = [\n [8, 3.5, \"fisheye\", { distortion: 0.5, astigmatism: 1.5, fieldCurvature: 1.5, lateralColor: 2, vignetting: 0.8, strayLight: 1.5, breathing: 0.3, closeFocus: 0.5 },\n \"Circular fisheye: astigmatism, field curvature and lateral colour grow towards the image circle; the exposed front element flares more; little breathing at 8 mm. Its distortion is the small departure from the equisolid projection.\", \"Circular fisheye; softer toward the rim\", \"circular\"],\n [10, 3.2, \"standard\", { distortion: -2.5, astigmatism: 1.4, fieldCurvature: 1.3, lateralColor: 1.8, vignetting: 1.4, strayLight: 1.3 },\n \"Rectilinear ultra-wide: strong barrel distortion, softer corners, lateral colour and corner fall-off.\", \"Strong barrel distortion; softer corners\"],\n [15, 2.8, \"fisheye\", { distortion: 0.5, astigmatism: 1.4, fieldCurvature: 1.3, lateralColor: 1.8, vignetting: 0.9, strayLight: 1.5, breathing: 0.3, closeFocus: 0.5 },\n \"Full-frame fisheye: as the circular fisheye, with its image filling the frame.\", \"Full-frame fisheye; softer edges\", \"full-frame\"],\n [16, 1.8, \"standard\", { distortion: -1.8, coma: 1.4, astigmatism: 1.3, lateralColor: 1.5, vignetting: 1.5, strayLight: 1.2 },\n \"Fast ultra-wide: coma and corner fall-off wide open, barrel distortion.\", \"Fast ultra-wide; coma wide open\"],\n [20, 1.4, \"standard\", { distortion: -1.3, coma: 1.4, astigmatism: 1.2, lateralColor: 1.3, vignetting: 1.4 }, \"Fast wide angle: coma wide open, barrel distortion.\", \"Fast wide angle; coma wide open\"],\n [24, 1.4, \"standard\", { distortion: -1, coma: 1.3, astigmatism: 1.1, lateralColor: 1.2, vignetting: 1.3 }, \"Fast wide angle: some coma wide open.\", \"Fast wide angle; some coma wide open\"],\n [35, 1.2, \"standard\", { distortion: -0.6, spherical: 1.3, coma: 1.2, axialColor: 1.3, vignetting: 1.3 }, \"Very fast: spherical aberration and axial colour wide open.\", \"Very fast; axial color wide open\"],\n [50, 1.2, \"standard\", { distortion: -0.3, spherical: 1.3, axialColor: 1.3, vignetting: 1.2 }, \"Very fast normal lens: spherical aberration and axial colour wide open; nearly no distortion.\", \"Very fast; nearly no distortion\"],\n [85, 1.2, \"standard\", { distortion: 0.3, spherical: 1.2, axialColor: 1.4, coma: 0.8, astigmatism: 0.7, vignetting: 1.1 },\n \"Very fast short telephoto: axial colour wide open, a small field angle so less coma and astigmatism; slight pincushion.\", \"Even across the frame; axial color\"],\n [100, 2.8, \"macro\", { distortion: 0.1, spherical: 0.8, coma: 0.7, astigmatism: 0.7, fieldCurvature: 0.5, axialColor: 0.8, vignetting: 0.6, breathing: 1.5, closeFocus: 0.2 },\n \"Macro: a flat field and corrections optimised for close focus (to 1:1), but strong breathing.\", \"Flat field to 1:1; strong breathing\"],\n [200, 2, \"standard\", { distortion: 0.4, axialColor: 1.5, lateralColor: 0.8, coma: 0.6, astigmatism: 0.6, vignetting: 0.9 }, \"Fast long telephoto: axial colour; a small field angle.\", \"Fast telephoto; axial color wide open\"],\n [300, 2.8, \"standard\", { distortion: 0.4, axialColor: 1.4, coma: 0.5, astigmatism: 0.5, vignetting: 0.8 }, \"Long telephoto: axial colour; a small field angle.\", \"Long telephoto; axial color\"],\n [400, 2.8, \"standard\", { distortion: 0.4, axialColor: 1.5, coma: 0.5, astigmatism: 0.5, vignetting: 0.9 }, \"Fast super telephoto: axial colour.\", \"Fast super telephoto; axial color\"],\n [500, 4, \"standard\", { distortion: 0.3, axialColor: 1.2, coma: 0.5, astigmatism: 0.5, vignetting: 0.7 }, \"Super telephoto.\", \"Super telephoto; low coma\"],\n [600, 4, \"standard\", { distortion: 0.3, axialColor: 1.3, coma: 0.5, astigmatism: 0.5, vignetting: 0.8 }, \"Super telephoto.\", \"Super telephoto; low coma, axial color\"],\n [800, 5.6, \"standard\", { distortion: 0.3, axialColor: 1.2, coma: 0.5, astigmatism: 0.5, vignetting: 0.7 }, \"Super telephoto.\", \"Slow super telephoto; low coma\"],\n [1200, 8, \"standard\", { distortion: 0.2, axialColor: 1.1, coma: 0.5, astigmatism: 0.5, vignetting: 0.6 }, \"Slow extreme telephoto: little vignetting.\", \"Slow extreme telephoto; little falloff\"]\n];\nconst CONSUMER: readonly Row[] = [\n [16, 2.8, \"standard\", { distortion: -1.5, coma: 1.1, astigmatism: 1.2, fieldCurvature: 1.2, lateralColor: 1.3, vignetting: 1.2, strayLight: 1.2 },\n \"Ultra-wide: strong barrel distortion and soft corners, left to the correction profile.\", \"Strong barrel distortion; soft corners\"],\n [20, 2, \"standard\", { distortion: -1.2, coma: 1.15, astigmatism: 1.1, lateralColor: 1.2, vignetting: 1.15 }, \"Wide angle: barrel distortion, coma wide open.\", \"Barrel distortion; coma wide open\"],\n [24, 1.8, \"standard\", { distortion: -1, coma: 1.1, lateralColor: 1.1, vignetting: 1.1 }, \"Wide angle: barrel distortion.\", \"Barrel distortion; mild corner falloff\"],\n [35, 1.8, \"standard\", { distortion: -0.6 }, \"The consumer baseline.\", \"The consumer baseline; mild barrel\"],\n [50, 1.8, \"standard\", { distortion: -0.3, spherical: 0.7, coma: 0.7, astigmatism: 0.7, fieldCurvature: 0.7, axialColor: 0.8, lateralColor: 0.6, vignetting: 0.8 },\n \"A simple, well-corrected normal design: quite good for its set (owner's example).\", \"Simple, well-corrected normal lens\"],\n [85, 1.8, \"standard\", { distortion: 0.3, spherical: 0.9, axialColor: 1.1, coma: 0.8, astigmatism: 0.8 }, \"Short telephoto: axial colour wide open.\", \"Short telephoto; axial color wide open\"],\n [100, 2, \"standard\", { distortion: 0.4, axialColor: 1.2, coma: 0.85 }, \"Short telephoto: axial colour wide open.\", \"Short telephoto; axial color, pincushion\"],\n [200, 5.6, \"standard\", { distortion: 0.6, axialColor: 0.9, lateralColor: 1.2, coma: 0.8, astigmatism: 0.9, vignetting: 0.8 }, \"Slow telephoto: lateral colour, pincushion.\", \"Lateral color and pincushion\"],\n [300, 5.6, \"standard\", { distortion: 0.6, lateralColor: 1.3 }, \"Slow telephoto: lateral colour, pincushion.\", \"Lateral color and pincushion\"],\n [400, 5.6, \"standard\", { distortion: 0.6, lateralColor: 1.3, axialColor: 1.1 }, \"Slow super telephoto: lateral and axial colour.\", \"Slow super telephoto; lateral color\"],\n [500, 5.6, \"standard\", { distortion: 0.6, lateralColor: 1.4, axialColor: 1.15, astigmatism: 1.1, fieldCurvature: 1.1 }, \"Slow super telephoto: softer edges.\", \"Slow super telephoto; softer edges\"],\n [600, 6.3, \"standard\", { distortion: 0.6, spherical: 1.2, coma: 1.2, astigmatism: 1.25, fieldCurvature: 1.25, lateralColor: 1.4, axialColor: 1.2, vignetting: 1.2 },\n \"Soft at the edges and wide open (owner's example).\", \"Soft at the edges and wide open\"],\n [800, 9, \"standard\", { distortion: 0.5, astigmatism: 1.15, fieldCurvature: 1.15, lateralColor: 1.4, axialColor: 1.1 }, \"Slow extreme telephoto: softer edges, lateral colour.\", \"Slow extreme telephoto; softer edges\"]\n];\n\nfunction entry(set: GenericLensSet, [focal, widest, kind, adjustments, note, summary, fisheye]: Row): GenericLensCatalogueEntry {\n const special = kind === \"macro\" ? \" Macro\" : kind === \"fisheye\" ? \" Fisheye\" : \"\";\n const suffix = set === \"professional\" ? \" Pro\" : \"\";\n return Object.freeze({ lensId: (\"pv-\" + focal + \"-\" + widest + special + suffix).toLowerCase().replace(/ /g, \"-\"), name: \"PV \" + focal + \" \" + widest + special + suffix,\n set, kind, equivalentFocalLengthMm: focal, widestFNumber: widest, narrowestFNumber: narrowest(focal),\n maximumMagnification: kind === \"macro\" ? 1 : GENERIC_PRIME_MINIMUM_FOCUS.magnification, projection: kind === \"fisheye\" ? \"fisheye-equisolid-angle\" as const : \"rectilinear\" as const,\n ...(fisheye ? { fisheyeImage: fisheye } : {}), adjustments: Object.freeze({ ...adjustments }), note, summary });\n}\n\n/** The 30 lenses: 17 professional, then 13 consumer, each set by focal length. */\nexport const GENERIC_LENS_CATALOGUE: readonly GenericLensCatalogueEntry[] = /* @__PURE__ */ Object.freeze([...PRO.map((row) => entry(\"professional\", row)),\n ...CONSUMER.map((row) => entry(\"consumer\", row))]);\n/** The catalogue ids, in catalogue order. */\nexport const GENERIC_LENS_IDS: readonly string[] = /* @__PURE__ */ Object.freeze(GENERIC_LENS_CATALOGUE.map((lens) => lens.lensId));\n\n/** Where the catalogue's numbers come from: Photivra's own choices, guided by openly published descriptions of lens behaviour. */\nexport const GENERIC_LENS_EVIDENCE: readonly EvidenceProvenance[] = /* @__PURE__ */ Object.freeze([\n /* @__PURE__ */ Object.freeze({ sourceOrigin: \"photivra\" as const, sourceReference: \"photivra:generic-lens-catalogue:549\", reuseStatus: \"photivra-owned\" as const,\n note: \"Coefficients chosen by Photivra (#549); not measured, not modelled on any product.\" }),\n /* @__PURE__ */ Object.freeze({ sourceOrigin: \"third-party\" as const, sourceReference: \"Wyant & Creath 1992, Basic Wavefront Aberration Theory for Optical Metrology, Applied Optics and Optical Engineering XI ch. 1 (free author copy)\",\n reuseStatus: \"factual-reference-only\" as const, note: \"Seidel wave-aberration expansion, pupil scaling and balanced spherical aberration; no data reproduced.\" }),\n /* @__PURE__ */ Object.freeze({ sourceOrigin: \"third-party\" as const, sourceReference: \"Wikipedia: Vignetting; Distortion (optics); Chromatic aberration; Coma (optics); Lens flare; Focus breathing\",\n reuseStatus: \"factual-reference-only\" as const, note: \"Qualitative descriptions of how these effects vary with lens class and aperture; no data reproduced.\" })\n]);\n\n/** Normalised Cauchy dispersion D(λ) = (1/λ² − 1/λ_d²)/(1/λ_F² − 1/λ_C²): 0 at the d line, F minus C = 1. */\nexport function genericLensDispersion(wavelengthNm: number): number {\n const inv = (nm: number): number => 1 / (nm * nm);\n return (inv(wavelengthNm) - inv(FRAUNHOFER_D_LINE_NM)) / (inv(FRAUNHOFER_F_LINE_NM) - inv(FRAUNHOFER_C_LINE_NM));\n}\n\n/** A catalogue lens's character resolved for a sensor, an f-number and a focus distance. Aberrations in waves at 550 nm. */\nexport interface GenericLensCharacter {\n fNumber: number;\n focusDistanceM: number | null;\n /** Thin-lens magnification at the focus distance (0 at infinity). */\n magnification: number;\n /** 1 + c·m/m_max. */\n closeFocusGain: number;\n /** Pupil-radius ratio N0/N. */\n pupilScale: number;\n /** Resolved Seidel coefficients at the frame corner (H = 1), waves at 550 nm; `balancingDefocusWaves` = −spherical. */\n aberrations: { sphericalWaves: number; balancingDefocusWaves: number; comaWaves: number; astigmatismWaves: number; fieldCurvatureWaves: number; axialColorWaves: number };\n /** Lateral colour: F-to-C image-radius difference at the corner, fraction of the half-diagonal and micrometres on this sensor. */\n lateralColor: { fraction: number; cornerMicrometers: number };\n /** Radial distortion profile (normalised to the half-diagonal, valid to 1.25 of it, beyond the corner) and its corner value (fraction; negative = barrel). */\n distortion: { profile: RadialDistortionProfile; corner: number };\n /** Relative illumination profile (normalised to the half-diagonal, valid to 1.25 of it) and the corner fall-off in stops. */\n vignetting: { profile: IlluminationVignettingProfile; cornerStops: number };\n strayLight: { veilingGlareFraction: number; ghostFraction: number };\n /** Field-of-view change at this focus and at minimum focus, beyond thin-lens unit focusing (fraction, negative = narrower). */\n breathing: { fieldOfViewChange: number; atMinimumFocus: number };\n /** Standard deviation of the autofocus error as a fraction of the depth of focus 2·N·c. */\n focusScatterDepthOfFocusFraction: number;\n}\n\nexport interface ResolvedGenericLens {\n lensId: string;\n name: string;\n set: GenericLensSet;\n kind: GenericLensKind;\n equivalentFocalLengthMm: number;\n /** Physical focal length on this sensor: equivalent ÷ crop factor. */\n focalLengthMm: number;\n cropFactor35Mm: number;\n /** Half-diagonal of the sensor, mm: field height H = 1 and ρ = 1 of the profiles. */\n halfDiagonalMm: number;\n widestFNumber: number;\n narrowestFNumber: number;\n /** Marked third-stop f-numbers within the range. */\n fNumbers: readonly number[];\n /** max(0.10 m, f·(1 + 1/m_max)), metres from the lens (thin lens). */\n minimumFocusDistanceM: number;\n maximumMagnification: number;\n projection: LensProjectionModel;\n fisheyeImage: \"circular\" | \"full-frame\" | null;\n /** Fisheyes: the radius of the 180° image circle on this sensor, 2·f·sin 45° (equisolid), mm. Null otherwise. */\n imageCircleRadiusMm: number | null;\n character: GenericLensCharacter;\n note: string;\n /** The catalogue entry's `summary`: a short plain-English description of the lens's character (at most 40 characters). */\n summary: string;\n evidence: readonly EvidenceProvenance[];\n scientificStatus: \"approximation\";\n limitations: readonly string[];\n}\n\nexport interface ResolveGenericLensInput {\n /** A catalogue id (`GENERIC_LENS_IDS`). */\n lensId: string;\n /** The sensor's active capture area, mm. */\n activeImagingArea: { widthMm: number; heightMm: number };\n /** Default: the widest aperture. Must lie in the lens's range. */\n fNumber?: number;\n /** Default: infinity. At least the minimum focus distance. */\n focusDistanceM?: number;\n}\n\n/** @internal The catalogue entry of an id, or a domain error naming the ids. */\nexport function genericLensEntry(lensId: unknown): GenericLensCatalogueEntry {\n const found = typeof lensId === \"string\" ? GENERIC_LENS_CATALOGUE.find((lens) => lens.lensId === lensId) : undefined;\n if (!found) throw new InvalidConfigurationError(\"lensId must be a generic lens catalogue id (GENERIC_LENS_IDS), for example \\\"pv-50-1.2-pro\\\" or \\\"pv-50-1.8\\\".\");\n return found;\n}\n\n/** @internal The baseline with the lens's adjustments applied (at its widest aperture, infinity focus). */\nexport function genericLensAdjustedValues(lens: GenericLensCatalogueEntry): GenericLensCharacterValues {\n const b = GENERIC_LENS_SET_BASELINES[lens.set], a = lens.adjustments, k = (key: keyof GenericLensAdjustments): number => a[key] ?? 1;\n return { sphericalWaves: b.sphericalWaves * k(\"spherical\"), comaWaves: b.comaWaves * k(\"coma\"), astigmatismWaves: b.astigmatismWaves * k(\"astigmatism\"),\n fieldCurvatureWaves: b.fieldCurvatureWaves * k(\"fieldCurvature\"), axialColorWaves: b.axialColorWaves * k(\"axialColor\"),\n lateralColorFraction: b.lateralColorFraction * k(\"lateralColor\"), distortionMagnitude: b.distortionMagnitude * a.distortion,\n naturalVignettingStops: b.naturalVignettingStops * k(\"vignetting\"), opticalVignettingStops: b.opticalVignettingStops * k(\"vignetting\"),\n veilingGlareFraction: b.veilingGlareFraction * k(\"strayLight\"), ghostFraction: b.ghostFraction * k(\"strayLight\"), breathingFraction: b.breathingFraction * k(\"breathing\"),\n focusScatterDepthOfFocusFraction: b.focusScatterDepthOfFocusFraction * k(\"focusScatter\"), closeFocusFactor: b.closeFocusFactor * k(\"closeFocus\") };\n}\n\n/** @internal E(ρ) = (1 − bρ²)² with E(1) = 2^−stops, valid to `maximumNormalizedRadius` (which must stay inside the first zero). */\nexport function genericLensVignettingProfile(cornerStops: number, halfDiagonalMm: number, maximumNormalizedRadius = 1): IlluminationVignettingProfile {\n const b = 1 - 2 ** (-cornerStops / 2);\n return { normalizationRadiusMm: halfDiagonalMm, maximumNormalizedRadius, coefficients: { r2: -2 * b, r4: b * b, r6: 0 } };\n}\n/** @internal r_d = r(1 + k1 ρ²) normalised to the half-diagonal. */\nexport function genericLensDistortionProfile(corner: number, halfDiagonalMm: number, maximumNormalizedRadius = 1): RadialDistortionProfile {\n return { normalizationRadiusMm: halfDiagonalMm, maximumNormalizedRadius, coefficients: { k1: corner, k2: 0, k3: 0 } };\n}\n\n/**\n * @internal How far the profiles `resolveGenericLens` returns reach, in units of the half-diagonal. A request that reads the scene\n * through them (`simulateCapture`, `simulateDisplayView`) needs them beyond the frame's corner: the outer site centres plus the\n * kernel radius and the query offsets. 1.25 covers a kernel reaching a quarter of the half-diagonal past the corner (on full\n * frame 5.4 mm, beyond the 512-site disc at 6 µm). Every catalogue lens stays valid there: its vignetting E = (1 − bρ²)² keeps\n * bρ² ≤ 0.83 (b ≤ 0.53, the 2.16-stop corner of PV 16 2.8), and its distortion stays monotonic (1 + 3k1ρ² ≥ 0.75 at k1 = −0.045).\n * Only the operating range grows; the coefficients, and so every value inside the frame, are unchanged.\n */\nexport const GENERIC_LENS_RESOLVED_PROFILE_REACH = 1.25;\n/** The distortion profile's range for that reach: with barrel distortion an image point at ρ_d comes from ρ_d/(1 + k1ρ²), farther out. */\nfunction resolvedDistortionReach(k1: number): number {\n const r = GENERIC_LENS_RESOLVED_PROFILE_REACH;\n return k1 < 0 ? r / (1 + k1 * r * r) * 1.01 : r;\n}\n\nfunction positiveFinite(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) throw new InvalidScientificInputError(name + \" must be a positive finite number.\");\n return value;\n}\n\n/**\n * Resolves a catalogue lens for a sensor: its focal length (equivalent ÷ the diagonal crop factor), f-number range, close focus\n * and projection, and its character (aberrations, colour, distortion, vignetting, flare, breathing and focus scatter) at the\n * given f-number and focus distance. Every value is a labelled approximation of a generic lens, not a measured product.\n */\nexport function resolveGenericLens(input: ResolveGenericLensInput): ResolvedGenericLens {\n requireInputObject(input, \"resolveGenericLens input\");\n requireInputKeys(input, \"resolveGenericLens\", [\"lensId\", \"activeImagingArea\", \"fNumber\", \"focusDistanceM\"]);\n const lens = genericLensEntry(input.lensId);\n requireInputObject(input.activeImagingArea, \"activeImagingArea\");\n const widthMm = positiveFinite(input.activeImagingArea.widthMm, \"activeImagingArea.widthMm\"), heightMm = positiveFinite(input.activeImagingArea.heightMm, \"activeImagingArea.heightMm\");\n const crop = calculateEquivalentFocalLength35Mm({ focalLengthMm: 1, activeImagingArea: { widthMm, heightMm } }).value.cropFactor35Mm;\n const focalLengthMm = lens.equivalentFocalLengthMm / crop, f = focalLengthMm / 1000, halfDiagonalMm = Math.hypot(widthMm, heightMm) / 2;\n const minimumFocusDistanceM = Math.max(GENERIC_PRIME_MINIMUM_FOCUS.minimumDistanceM, f * (1 + 1 / lens.maximumMagnification));\n const fNumber = input.fNumber === undefined ? lens.widestFNumber : positiveFinite(input.fNumber, \"fNumber\");\n if (fNumber < lens.widestFNumber || fNumber > lens.narrowestFNumber)\n throw new InvalidScientificInputError(\"fNumber \" + fNumber + \" is outside the \" + lens.name + \" range f/\" + lens.widestFNumber + \"–f/\" + lens.narrowestFNumber + \".\");\n const focusDistanceM = input.focusDistanceM === undefined ? null : positiveFinite(input.focusDistanceM, \"focusDistanceM\");\n if (focusDistanceM !== null && focusDistanceM < minimumFocusDistanceM)\n throw new InvalidScientificInputError(\"focusDistanceM \" + focusDistanceM + \" m is closer than the \" + lens.name + \" minimum focus distance \" + minimumFocusDistanceM + \" m.\");\n const values = genericLensAdjustedValues(lens), maximumMagnification = f / (minimumFocusDistanceM - f);\n const magnification = focusDistanceM === null ? 0 : f / (focusDistanceM - f), closeness = Math.min(1, magnification / maximumMagnification);\n const closeFocusGain = 1 + values.closeFocusFactor * closeness, a = lens.widestFNumber / fNumber;\n const cornerStops = values.naturalVignettingStops + values.opticalVignettingStops * Math.min(1, Math.max(0, 1 - Math.log2(fNumber / lens.widestFNumber) / 2));\n const character: GenericLensCharacter = { fNumber, focusDistanceM, magnification, closeFocusGain, pupilScale: a,\n aberrations: { sphericalWaves: values.sphericalWaves * a ** 4 * closeFocusGain, balancingDefocusWaves: -values.sphericalWaves * a ** 4 * closeFocusGain,\n comaWaves: values.comaWaves * a ** 3 * closeFocusGain, astigmatismWaves: values.astigmatismWaves * a * a * closeFocusGain,\n fieldCurvatureWaves: values.fieldCurvatureWaves * a * a * closeFocusGain, axialColorWaves: values.axialColorWaves * a * a * closeFocusGain },\n lateralColor: { fraction: values.lateralColorFraction, cornerMicrometers: values.lateralColorFraction * halfDiagonalMm * 1000 },\n distortion: { profile: genericLensDistortionProfile(values.distortionMagnitude, halfDiagonalMm, resolvedDistortionReach(values.distortionMagnitude)), corner: values.distortionMagnitude },\n vignetting: { profile: genericLensVignettingProfile(cornerStops, halfDiagonalMm, GENERIC_LENS_RESOLVED_PROFILE_REACH), cornerStops },\n strayLight: { veilingGlareFraction: values.veilingGlareFraction, ghostFraction: values.ghostFraction },\n breathing: { fieldOfViewChange: values.breathingFraction * closeness, atMinimumFocus: values.breathingFraction },\n focusScatterDepthOfFocusFraction: values.focusScatterDepthOfFocusFraction };\n const fisheye = lens.kind === \"fisheye\";\n return Object.freeze({ lensId: lens.lensId, name: lens.name, set: lens.set, kind: lens.kind, equivalentFocalLengthMm: lens.equivalentFocalLengthMm, focalLengthMm,\n cropFactor35Mm: crop, halfDiagonalMm, widestFNumber: lens.widestFNumber, narrowestFNumber: lens.narrowestFNumber,\n fNumbers: Object.freeze(GENERIC_PRIME_THIRD_STOP_F_NUMBERS.filter((n) => n >= lens.widestFNumber && n <= lens.narrowestFNumber)),\n minimumFocusDistanceM, maximumMagnification, projection: lens.projection, fisheyeImage: lens.fisheyeImage ?? null,\n imageCircleRadiusMm: fisheye ? 2 * focalLengthMm * Math.SQRT1_2 : null, character, note: lens.note, summary: lens.summary, evidence: GENERIC_LENS_EVIDENCE,\n scientificStatus: \"approximation\" as const, limitations: Object.freeze([\n \"A generic Photivra lens, not a real product: every coefficient is a Photivra choice that reproduces typical behaviour of its class, not a measurement.\",\n \"Primary (Seidel) aberrations only, scaled with the pupil radius and a generic close-focus factor; no higher-order terms, no zoom or focus-group design.\",\n \"Focal length, close focus and magnification are thin-lens values; the lens design is scaled to the sensor by the crop factor with its wavefront error in waves kept.\",\n \"Flare, focus breathing and focus scatter are reported numbers; simulateCapture renders none of them.\"]) });\n}\n\nexport interface SimulateGenericLensFocusErrorInput {\n lensId: string;\n activeImagingArea: { widthMm: number; heightMm: number };\n fNumber: number;\n /** The intended focus distance, m. */\n focusDistanceM: number;\n /** uint32 seed of the shooting session. */\n seed: number;\n /** uint32 index of the shot in the session. Default 0. */\n shotIndex?: number;\n /** Circle of confusion for the depth of focus, mm. Default: the sensor diagonal / 1500. */\n circleOfConfusionMm?: number;\n}\n\nexport interface GenericLensFocusError {\n lensId: string;\n /** The intended and the sampled focus distance, m (the sampled one held within minimum focus and a far limit). */\n intendedFocusDistanceM: number;\n focusDistanceM: number;\n /** The standard normal draw z and the image-side focus error z·σ·2Nc, µm (positive: a longer image distance, so focused nearer). */\n standardNormal: number;\n imageSideErrorMicrometers: number;\n /** σ (fraction of the depth of focus) and the depth of focus 2·N·c, µm. */\n depthOfFocusFraction: number;\n depthOfFocusMicrometers: number;\n /** True when the sampled image distance was held at the minimum-focus or the far (1000 m) limit. */\n held: \"minimum-focus\" | \"far-limit\" | null;\n scientificStatus: \"approximation\";\n}\n\n/** Stream id of the focus-scatter draw (\"FOCS\"), kept apart from the sensor noise streams 0 and 1. */\nconst FOCUS_SCATTER_STREAM_ID = 0x464f4353;\n/** The far limit of a sampled focus distance, m. */\nconst FOCUS_SCATTER_FAR_LIMIT_M = 1000;\n\nfunction uint32(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value < 0 || value > 0xffffffff) throw new InvalidScientificInputError(name + \" must be a uint32 integer.\");\n return value;\n}\n\n/**\n * Focus consistency (#549): a seeded, reproducible autofocus error for one shot. The image-side error is z·σ·2Nc, with z a\n * standard normal draw from (seed, shotIndex) (MurmurHash3 stream seed, xorshift32 and Box–Muller, the generators of the\n * capture's sensor noise), σ the lens's focus scatter as a fraction of the depth of focus 2·N·c, and\n * c the circle of confusion. The thin-lens image distance of the intended focus moves by that error and the focus distance is\n * read back from it. An approximation of autofocus variation; off unless called (or `simulateCapture`'s `lens.focusScatter`).\n */\nexport function simulateGenericLensFocusError(input: SimulateGenericLensFocusErrorInput): GenericLensFocusError {\n requireInputObject(input, \"simulateGenericLensFocusError input\");\n requireInputKeys(input, \"simulateGenericLensFocusError\", [\"lensId\", \"activeImagingArea\", \"fNumber\", \"focusDistanceM\", \"seed\", \"shotIndex\", \"circleOfConfusionMm\"]);\n const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea, fNumber: input.fNumber, focusDistanceM: input.focusDistanceM });\n const seed = uint32(input.seed, \"seed\"), shot = input.shotIndex === undefined ? 0 : uint32(input.shotIndex, \"shotIndex\");\n const c = input.circleOfConfusionMm === undefined ? 2 * lens.halfDiagonalMm / 1500 : positiveFinite(input.circleOfConfusionMm, \"circleOfConfusionMm\");\n const z = sampleDeterministicStandardNormal(createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, shot, FOCUS_SCATTER_STREAM_ID)));\n const sigma = lens.character.focusScatterDepthOfFocusFraction, depthOfFocusMm = 2 * lens.character.fNumber * c, errorMm = z * sigma * depthOfFocusMm;\n const f = lens.focalLengthMm, imageDistance = (d: number): number => 1 / (1 / f - 1 / (d * 1000));\n const nearest = imageDistance(lens.minimumFocusDistanceM), farthest = imageDistance(FOCUS_SCATTER_FAR_LIMIT_M);\n const v = imageDistance(input.focusDistanceM) + errorMm, held = v > nearest ? \"minimum-focus\" as const : v < farthest ? \"far-limit\" as const : null;\n const vHeld = Math.min(nearest, Math.max(farthest, v));\n const focusDistanceM = held === \"minimum-focus\" ? lens.minimumFocusDistanceM : held === \"far-limit\" ? FOCUS_SCATTER_FAR_LIMIT_M : 1 / (1 / f - 1 / vHeld) / 1000;\n return Object.freeze({ lensId: lens.lensId, intendedFocusDistanceM: input.focusDistanceM, focusDistanceM, standardNormal: z, imageSideErrorMicrometers: errorMm * 1000,\n depthOfFocusFraction: sigma, depthOfFocusMicrometers: depthOfFocusMm * 1000, held, scientificStatus: \"approximation\" as const });\n}\n"]}
|