@photivra/engine 2.15.0 → 2.17.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 +102 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +38 -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/capture/native-environment-lean.js +1 -1
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-raw-development.d.ts +15 -0
- package/dist/capture/native-raw-development.js +12 -6
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/capture/raw-development-kernel.d.ts +10 -3
- package/dist/capture/raw-development-kernel.js +25 -6
- package/dist/capture/raw-development-kernel.js.map +1 -1
- package/dist/core/internal-radiance-evaluator.d.ts +5 -2
- package/dist/core/internal-radiance-evaluator.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/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 +118 -0
- package/dist/facade/defocus-image-space-provider.js +448 -0
- package/dist/facade/defocus-image-space-provider.js.map +1 -0
- package/dist/facade/diffraction-provider.d.ts +45 -0
- package/dist/facade/diffraction-provider.js +120 -14
- 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 +30 -0
- package/dist/facade/display-view-lens-signal.js +427 -0
- package/dist/facade/display-view-lens-signal.js.map +1 -0
- package/dist/facade/display-view.d.ts +21 -0
- package/dist/facade/display-view.js +66 -8
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +7 -0
- package/dist/facade/expected-signal-setup.js +12 -2
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +8 -0
- package/dist/facade/expected-signal.js +150 -0
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +6 -0
- package/dist/facade/generic-lens-capture.js +8 -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 +148 -6
- package/dist/facade/simulate-capture.js +521 -88
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +29 -19
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +2 -9
- package/dist/gpu/display-view-quality-gpu.js +63 -13
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +2 -0
- package/dist/gpu/display-view-speed-gpu.js +47 -16
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-accelerator.js +3 -0
- package/dist/gpu/image-kernel-accelerator.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -0
- package/dist/gpu/wgsl-display-view-quality-kernel.js +53 -2
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +15 -0
- package/dist/gpu/wgsl-display-view-speed-kernel.js +42 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- 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 +44 -21
- package/dist/optics/defocus-disc-kernel.js +47 -25
- 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 +37 -0
- package/dist/optics/sensor-environment-query.js +58 -19
- 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/dist/processing/development-detail.d.ts +132 -0
- package/dist/processing/development-detail.js +325 -0
- package/dist/processing/development-detail.js.map +1 -0
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +54 -1
- package/docs/QUICK_START.md +444 -26
- package/docs/USAGE.md +71 -8
- package/docs/api/capture-native-raw-development.md +16 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +281 -0
- package/docs/api/exports.json +162 -2
- package/docs/api/facade-simulate-capture.md +151 -5
- package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
- package/docs/api/optics-defocus-disc-kernel.md +25 -17
- package/docs/api/optics-sensor-environment-query.md +6 -0
- package/docs/api/output-generic-lens-corrections.md +39 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
package/docs/USAGE.md
CHANGED
|
@@ -1455,7 +1455,7 @@ console.log(blur.value.diameterMm);
|
|
|
1455
1455
|
|
|
1456
1456
|
You supply the circle-of-confusion criterion used for depth of field (DOF). These are ideal geometric thin-lens models. They do not include diffraction or real-lens aberrations in the DOF criterion.
|
|
1457
1457
|
|
|
1458
|
-
`simulateCapture` and the display view render the defocus blur disc with `lens.defocus: { model: "geometric-disc" }`, up to a kernel radius of `DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES` (
|
|
1458
|
+
`simulateCapture` and the display view render the defocus blur disc with `lens.defocus: { model: "geometric-disc" }`, up to a kernel radius of `DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES` (512 sites, a disc 1025 sites across, 6.15 mm at 6 µm; 64 sites before 2.17.0) and refuse a larger one. Up to 64 sites the disc is convolved directly, as before; above it, by runs of equal kernel weights (#564), the same sums at a cost that grows with the radius instead of its square ([Quick start, Large defocus discs](QUICK_START.md#large-defocus-discs-564)). Use `calculateDefocusDiscKernelRadius()` to check a request before rendering it. It runs the capture's own resolution and limit test, so it agrees with the capture's refusal, and `convolution` says which method renders it. Pass the f-number the capture uses (in an automatic exposure mode, the resolved one) and the sensor's pixel pitch:
|
|
1459
1459
|
|
|
1460
1460
|
```ts
|
|
1461
1461
|
import { calculateDefocusDiscKernelRadius } from "@photivra/engine";
|
|
@@ -1463,13 +1463,14 @@ import { calculateDefocusDiscKernelRadius } from "@photivra/engine";
|
|
|
1463
1463
|
const check = calculateDefocusDiscKernelRadius({
|
|
1464
1464
|
focalLengthMm: 50,
|
|
1465
1465
|
aperture: 1.4,
|
|
1466
|
-
focusDistanceM:
|
|
1466
|
+
focusDistanceM: 0.25,
|
|
1467
1467
|
subjectDistanceM: 2,
|
|
1468
1468
|
pixelPitchMicrometers: 6
|
|
1469
1469
|
});
|
|
1470
1470
|
|
|
1471
|
-
console.log(check.value.withinLimit); // false: lens.defocus refuses this request
|
|
1472
|
-
console.log(check.value.kernelRadiusSites, check.value.excessKernelRadiusSites); //
|
|
1471
|
+
console.log(check.value.withinLimit); // false: lens.defocus refuses this request (a 7.81 mm blur, 1,303 sites across)
|
|
1472
|
+
console.log(check.value.kernelRadiusSites, check.value.excessKernelRadiusSites); // 651, 139
|
|
1473
|
+
// Focused at 1 m instead: 157 sites across, radius 78, withinLimit true, convolution "runs".
|
|
1473
1474
|
```
|
|
1474
1475
|
|
|
1475
1476
|
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#thin-lens-focus-and-depth-of-field).
|
|
@@ -3293,6 +3294,9 @@ void develop;
|
|
|
3293
3294
|
|
|
3294
3295
|
- `highlightMode: "reconstruct"` blends pixels above 0.8 of white towards neutral, instead of clipping
|
|
3295
3296
|
each WB-scaled channel. It is an approximation, not a recovery of the clipped signal.
|
|
3297
|
+
- `exposureCompensationStops` (#556, −2 to +5, default 0) multiplies each site's black-subtracted, white-normalized
|
|
3298
|
+
value by 2^stops before demosaic and white balance. A site pushed to white clips and counts as saturated; the RAW
|
|
3299
|
+
codes are not changed. 0 develops byte for byte as before. The WebGPU accelerator rejects a non-zero push.
|
|
3296
3300
|
- `extended-reinhard-luminance` takes `{ whiteLuminance }`, the luminance mapped to output white.
|
|
3297
3301
|
- `createPhotographicExportPair(input, { signal })` accepts an optional `AbortSignal`.
|
|
3298
3302
|
|
|
@@ -7467,6 +7471,28 @@ console.log(calculateGeometricFocusBreathing({
|
|
|
7467
7471
|
}).value.deltaDegrees);
|
|
7468
7472
|
```
|
|
7469
7473
|
|
|
7474
|
+
## Generic lens catalogue (#549)
|
|
7475
|
+
|
|
7476
|
+
`GENERIC_LENS_CATALOGUE` lists the engine's 30 generic primes (17 professional, 13 consumer, "PV 50 1.2 Pro", "PV 50 1.8"; Photivra's
|
|
7477
|
+
own designs, not real products). `resolveGenericLens` resolves one for a sensor, an f-number and a focus distance: its focal length
|
|
7478
|
+
(scaled by the crop factor), aperture range, close focus, projection and its character, Seidel aberrations, axial and lateral
|
|
7479
|
+
colour, distortion and vignetting profiles, flare, breathing and focus scatter, each a labelled approximation.
|
|
7480
|
+
`simulateGenericLensFocusError` draws a seeded autofocus error; `createGenericLensCorrectionProfile` builds the lens's generic
|
|
7481
|
+
correction profile for `resolveLensCorrectionPlan`. `simulateCapture` renders a catalogue lens with `lens.catalogue`
|
|
7482
|
+
([quick start](QUICK_START.md#generic-lens-catalogue-549)). Equations, tables and the chart measurements:
|
|
7483
|
+
[LENS_CATALOGUE.md](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md).
|
|
7484
|
+
|
|
7485
|
+
```ts
|
|
7486
|
+
import { GENERIC_LENS_CATALOGUE, resolveGenericLens, simulateGenericLensFocusError } from "@photivra/engine";
|
|
7487
|
+
|
|
7488
|
+
const frame = { widthMm: 36, heightMm: 24 };
|
|
7489
|
+
const pro = resolveGenericLens({ lensId: "pv-50-1.2-pro", activeImagingArea: frame, fNumber: 2 });
|
|
7490
|
+
const consumer = resolveGenericLens({ lensId: "pv-50-1.8", activeImagingArea: frame, fNumber: 2 });
|
|
7491
|
+
console.log(GENERIC_LENS_CATALOGUE.length, pro.character.aberrations.comaWaves < consumer.character.aberrations.comaWaves); // 30 true
|
|
7492
|
+
const shot = simulateGenericLensFocusError({ lensId: "pv-50-1.8", activeImagingArea: frame, fNumber: 1.8, focusDistanceM: 2, seed: 1, shotIndex: 0 });
|
|
7493
|
+
console.log(shot.focusDistanceM, shot.imageSideErrorMicrometers);
|
|
7494
|
+
```
|
|
7495
|
+
|
|
7470
7496
|
## Parallel native environment RAW (row-band partitions, 2.0)
|
|
7471
7497
|
|
|
7472
7498
|
`createNativeEnvironmentRawTask` runs on one thread. For several cores, split the capture into row bands with
|
|
@@ -7802,6 +7828,27 @@ import { simulateDisplayView } from "@photivra/engine";
|
|
|
7802
7828
|
const view = await simulateDisplayView({ scene: { preset: "slanted-edge" }, sensor: { pixelPitchMicrometers: 3 }, output: { width: 600, height: 400 } },
|
|
7803
7829
|
{ width: 60, height: 40 });
|
|
7804
7830
|
console.log(view.kind, view.view.scaleX, view.assumptions.find((a) => a.id === "view.definition")?.value);
|
|
7831
|
+
// #533: the real capture's clipped-highlight pixels in the rendered region, as the expected value of simulateCapture's count.
|
|
7832
|
+
console.log(view.diagnostics.highlight.saturatedPixelCount, view.diagnostics.highlight.statistic); // a real number, "expected-value"
|
|
7833
|
+
```
|
|
7834
|
+
|
|
7835
|
+
`diagnostics.highlight.saturatedPixelCount` is the expected number of the real capture's output pixels in the rendered
|
|
7836
|
+
region that have a clipped contributing site. `simulateCapture` counts the same pixels on its one noise sample, so the two
|
|
7837
|
+
are equal where every site is far from white. Counts of regions that tile a view add up to the whole view's count. See
|
|
7838
|
+
[Highlight count](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#highlight-count-533).
|
|
7839
|
+
|
|
7840
|
+
From #563 the view renders `lens.projection`, `lens.distortion`, `lens.vignetting` and camera rotation (global
|
|
7841
|
+
shutter), in focus and with `lens.defocus` out of focus, with each real site's expected electrons as the capture's. These
|
|
7842
|
+
requests run on the CPU (`gpu: "auto"` falls back labelled `unsupported-on-backend`); rotation with a rolling shutter is
|
|
7843
|
+
refused. See [Lens projection, distortion, vignetting and rotation](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#lens-projection-distortion-vignetting-and-rotation).
|
|
7844
|
+
|
|
7845
|
+
```ts
|
|
7846
|
+
import { simulateDisplayView } from "@photivra/engine";
|
|
7847
|
+
|
|
7848
|
+
const fisheye = await simulateDisplayView({ scene: { preset: "siemens-star" }, sensor: { pixelPitchMicrometers: 3 }, output: { width: 120, height: 80 },
|
|
7849
|
+
lens: { projection: "fisheye-equisolid-angle", vignetting: { normalizationRadiusMm: 0.25, maximumNormalizedRadius: 1, coefficients: { r2: -0.4, r4: 0, r6: 0 } } } },
|
|
7850
|
+
{ width: 30, height: 20 });
|
|
7851
|
+
console.log(fisheye.kind, fisheye.assumptions.find((a) => a.id === "lens.vignetting")?.kind); // "display-view" "model"
|
|
7805
7852
|
```
|
|
7806
7853
|
|
|
7807
7854
|
#### Rendering a prepared view on the GPU (2.11, #481)
|
|
@@ -7809,9 +7856,10 @@ console.log(view.kind, view.view.scaleX, view.assumptions.find((a) => a.id === "
|
|
|
7809
7856
|
`prepareDisplayView(request, { gpu })` chooses the renderer: `"off"` (the default) is the CPU reference, `"auto"` the GPU
|
|
7810
7857
|
when a conforming device is available and the request is supported (else the CPU, labelled), `"require"` fails instead of
|
|
7811
7858
|
falling back. `gpuDevice` hands the view a worker's `navigator.gpu` or an `ImageKernelAccelerator` whose device it shares.
|
|
7812
|
-
Every render says which path ran in `diagnostics.fast.execution`, in the accelerator's words.
|
|
7813
|
-
|
|
7814
|
-
|
|
7859
|
+
Every render says which path ran in `diagnostics.fast.execution`, in the accelerator's words. The engine's renderer runs the
|
|
7860
|
+
Speed (#478) and Quality (#479) kernels on a conforming device for the requests it covers. Elsewhere `"auto"` runs the CPU
|
|
7861
|
+
reference and gives the reason (in Node without a WebGPU entry point, for example); see
|
|
7862
|
+
[Rendering on the GPU](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#rendering-on-the-gpu).
|
|
7815
7863
|
|
|
7816
7864
|
```ts
|
|
7817
7865
|
import { prepareDisplayView, type SimulateCaptureOptions } from "@photivra/engine";
|
|
@@ -7912,7 +7960,7 @@ console.log(barrel.request.siteInput(0).environment.lensFieldMapping?.distortion
|
|
|
7912
7960
|
lattice, and the capture convolves the scene radiance with it, which is a geometric approximation. With
|
|
7913
7961
|
`lens.diffraction`, each spectral node's diffraction kernel is convolved with the disc, which is a further
|
|
7914
7962
|
approximation. The `lens.defocus` assumption records the diameter in µm and in sites, the kernel radius and the
|
|
7915
|
-
execution. `simulateDisplayView`
|
|
7963
|
+
execution. `simulateDisplayView` renders it too, also with projection, distortion, vignetting and rotation (#563). See [Optics](QUICK_START.md#optics).
|
|
7916
7964
|
|
|
7917
7965
|
```ts
|
|
7918
7966
|
import { simulateCapture } from "@photivra/engine";
|
|
@@ -7926,6 +7974,21 @@ const blurred = await simulateCapture({
|
|
|
7926
7974
|
console.log(blurred.assumptions.find((a) => a.id === "lens.defocus")?.value); // diameterSites ≈ 5.95, kernelRadiusSites 3
|
|
7927
7975
|
```
|
|
7928
7976
|
|
|
7977
|
+
Bokeh character (#553): `lens.defocus` also takes `blades` (a polygonal or rounded-blade opening), `sphericalAberration`
|
|
7978
|
+
(primary W040: a soft-edged blur on one side of the focus plane and a bright-rimmed one on the other) and `catsEye`
|
|
7979
|
+
(mechanical vignetting by field position; `simulateCapture` only). Each is off by default. `calculateDefocusBokehKernel`
|
|
7980
|
+
returns the kernel without rendering. See [Bokeh character](QUICK_START.md#bokeh-character-553).
|
|
7981
|
+
|
|
7982
|
+
```ts
|
|
7983
|
+
import { calculateDefocusBokehKernel } from "@photivra/engine";
|
|
7984
|
+
|
|
7985
|
+
const kernel = calculateDefocusBokehKernel({
|
|
7986
|
+
focalLengthMm: 50, aperture: 4, focusDistanceM: 1.4, subjectDistanceM: 2, pixelPitchMicrometers: 6,
|
|
7987
|
+
bokeh: { blades: { count: 5 }, sphericalAberration: { w040Micrometers: 0.5, atFNumber: 4 } }
|
|
7988
|
+
});
|
|
7989
|
+
console.log(kernel.value.side, kernel.value.kernelRadiusSites, kernel.value.method); // "behind-focus", 17, "pupil-ray-binning"
|
|
7990
|
+
```
|
|
7991
|
+
|
|
7929
7992
|
`motion.angularVelocityRadPerSec` rotates the camera during the exposure (8 time midpoints by
|
|
7930
7993
|
default). `motion.shutter: "rolling"` derives each site's exposure window from the body's rolling
|
|
7931
7994
|
readout, with `resolveElectronicRollingShutterExposureSchedule`. See [Motion](QUICK_START.md#motion).
|
|
@@ -31,6 +31,14 @@ export function createNativeRawDevelopmentTask(input: NativeRawDevelopmentInput,
|
|
|
31
31
|
|
|
32
32
|
This call is synchronous; any supplied provider must follow its explicit synchronous contract.
|
|
33
33
|
|
|
34
|
+
## NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS
|
|
35
|
+
|
|
36
|
+
Accepted range of `NativeRawDevelopmentInput.exposureCompensationStops` and `simulateCapture`'s `output.exposureCompensationStops` (#556).
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS = Object.freeze({ minimum: -2, maximum: 5 } as const)
|
|
40
|
+
```
|
|
41
|
+
|
|
34
42
|
## NativeRawDevelopmentInput
|
|
35
43
|
|
|
36
44
|
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
@@ -56,6 +64,14 @@ export interface NativeRawDevelopmentInput {
|
|
|
56
64
|
* always true for a 2×2 Bayer pattern. Interior pixels are unchanged.
|
|
57
65
|
*/
|
|
58
66
|
borderHandling?: RawReconstructionBorderHandling;
|
|
67
|
+
/**
|
|
68
|
+
* Development exposure push in stops (#556), from {@link NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS} −2 to +5; default 0.
|
|
69
|
+
* Each site's black-subtracted, white-normalized value is multiplied by 2^stops before demosaic, white balance, the
|
|
70
|
+
* highlight mode, colour and the tone curve, like a raw editor's exposure slider. A site whose pushed value reaches the
|
|
71
|
+
* white level is clipped there and counted as saturated (so `highlightMode: "reconstruct"` rolls it off); values below
|
|
72
|
+
* black stay signed. The RAW codes are not changed. 0 (or omitted) develops exactly as before, byte for byte.
|
|
73
|
+
*/
|
|
74
|
+
exposureCompensationStops?: number;
|
|
59
75
|
/** Counts the kept packed RAW plus the integer output. It does not count caller buffers, process heap or GPU memory. */
|
|
60
76
|
maximumRetainedPayloadBytes: number;
|
|
61
77
|
}
|
package/docs/api/core-version.md
CHANGED
|
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
|
|
|
11
11
|
own versions. Release checks confirm it matches package.json and the packed package.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
ENGINE_API_VERSION = "2.
|
|
14
|
+
ENGINE_API_VERSION = "2.17.0" as const
|
|
15
15
|
```
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# equipment/generic-lens-catalogue.ts public contracts
|
|
2
|
+
|
|
3
|
+
The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
|
|
4
|
+
|
|
5
|
+
## GENERIC_LENS_CATALOGUE
|
|
6
|
+
|
|
7
|
+
The 30 lenses: 17 professional, then 13 consumer, each set by focal length.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
GENERIC_LENS_CATALOGUE: readonly GenericLensCatalogueEntry[] = Object.freeze([...PRO.map((row) => entry("professional", row)),
|
|
11
|
+
...CONSUMER.map((row) => entry("consumer", row))])
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## GENERIC_LENS_IDS
|
|
15
|
+
|
|
16
|
+
The catalogue ids, in catalogue order.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
GENERIC_LENS_IDS: readonly string[] = Object.freeze(GENERIC_LENS_CATALOGUE.map((lens) => lens.lensId))
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## GENERIC_LENS_SET_BASELINES
|
|
23
|
+
|
|
24
|
+
The set baselines (owner's table, #549), each lens's starting point before its adjustments.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
GENERIC_LENS_SET_BASELINES: Readonly<Record<GenericLensSet, Readonly<GenericLensCharacterValues>>> = Object.freeze({
|
|
28
|
+
professional: Object.freeze({ sphericalWaves: 0.25, comaWaves: 0.35, astigmatismWaves: 0.3, fieldCurvatureWaves: 0.15, axialColorWaves: 0.15,
|
|
29
|
+
lateralColorFraction: 0.00008, distortionMagnitude: 0.01, naturalVignettingStops: 0.4, opticalVignettingStops: 0.6, veilingGlareFraction: 0.005, ghostFraction: 0.002,
|
|
30
|
+
breathingFraction: -0.03, focusScatterDepthOfFocusFraction: 0.1, closeFocusFactor: 0.15 }),
|
|
31
|
+
consumer: Object.freeze({ sphericalWaves: 0.5, comaWaves: 1.0, astigmatismWaves: 0.8, fieldCurvatureWaves: 0.45, axialColorWaves: 0.4,
|
|
32
|
+
lateralColorFraction: 0.0003, distortionMagnitude: 0.03, naturalVignettingStops: 0.7, opticalVignettingStops: 1.1, veilingGlareFraction: 0.02, ghostFraction: 0.008,
|
|
33
|
+
breathingFraction: -0.08, focusScatterDepthOfFocusFraction: 0.3, closeFocusFactor: 0.5 })
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## GenericLensAdjustments
|
|
38
|
+
|
|
39
|
+
Per-lens multipliers of the set baseline (missing: 1). `distortion` is signed and required: −1 barrel, +1 pincushion at the baseline.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
export type GenericLensAdjustments = { distortion: number } & Partial<Record<"spherical" | "coma" | "astigmatism" | "fieldCurvature" | "axialColor" | "lateralColor" |
|
|
43
|
+
"vignetting" | "strayLight" | "breathing" | "focusScatter" | "closeFocus", number>>;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## GenericLensCatalogueEntry
|
|
47
|
+
|
|
48
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export interface GenericLensCatalogueEntry {
|
|
52
|
+
/** Catalogue id, for example "pv-50-1.2-pro". */
|
|
53
|
+
lensId: string;
|
|
54
|
+
/** Display name, for example "PV 50 1.2 Pro". */
|
|
55
|
+
name: string;
|
|
56
|
+
set: GenericLensSet;
|
|
57
|
+
kind: GenericLensKind;
|
|
58
|
+
/** Full-frame-equivalent focal length (36 × 24 mm reference). */
|
|
59
|
+
equivalentFocalLengthMm: number;
|
|
60
|
+
widestFNumber: number;
|
|
61
|
+
narrowestFNumber: number;
|
|
62
|
+
/** Largest magnification: 0.15 (the generic rule) or 1 for the professional macro. */
|
|
63
|
+
maximumMagnification: number;
|
|
64
|
+
projection: LensProjectionModel;
|
|
65
|
+
/** Fisheyes only: a circular image inside the frame, or one that fills it. */
|
|
66
|
+
fisheyeImage?: "circular" | "full-frame";
|
|
67
|
+
adjustments: Readonly<GenericLensAdjustments>;
|
|
68
|
+
/** Why this lens departs from its set's baseline. */
|
|
69
|
+
note: string;
|
|
70
|
+
/**
|
|
71
|
+
* A short plain-English summary of the lens's character (at most 40 characters, US spelling), written from its adjustments and
|
|
72
|
+
* `note`, for lists and pickers. It describes only what the catalogue models (bokeh shape is not modeled: the defocus is a disc).
|
|
73
|
+
*/
|
|
74
|
+
summary: string;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## GenericLensCharacter
|
|
79
|
+
|
|
80
|
+
A catalogue lens's character resolved for a sensor, an f-number and a focus distance. Aberrations in waves at 550 nm.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
export interface GenericLensCharacter {
|
|
84
|
+
fNumber: number;
|
|
85
|
+
focusDistanceM: number | null;
|
|
86
|
+
/** Thin-lens magnification at the focus distance (0 at infinity). */
|
|
87
|
+
magnification: number;
|
|
88
|
+
/** 1 + c·m/m_max. */
|
|
89
|
+
closeFocusGain: number;
|
|
90
|
+
/** Pupil-radius ratio N0/N. */
|
|
91
|
+
pupilScale: number;
|
|
92
|
+
/** Resolved Seidel coefficients at the frame corner (H = 1), waves at 550 nm; `balancingDefocusWaves` = −spherical. */
|
|
93
|
+
aberrations: { sphericalWaves: number; balancingDefocusWaves: number; comaWaves: number; astigmatismWaves: number; fieldCurvatureWaves: number; axialColorWaves: number };
|
|
94
|
+
/** Lateral colour: F-to-C image-radius difference at the corner, fraction of the half-diagonal and micrometres on this sensor. */
|
|
95
|
+
lateralColor: { fraction: number; cornerMicrometers: number };
|
|
96
|
+
/** Radial distortion profile (normalised to the half-diagonal, valid to 1.25 of it, beyond the corner) and its corner value (fraction; negative = barrel). */
|
|
97
|
+
distortion: { profile: RadialDistortionProfile; corner: number };
|
|
98
|
+
/** Relative illumination profile (normalised to the half-diagonal, valid to 1.25 of it) and the corner fall-off in stops. */
|
|
99
|
+
vignetting: { profile: IlluminationVignettingProfile; cornerStops: number };
|
|
100
|
+
strayLight: { veilingGlareFraction: number; ghostFraction: number };
|
|
101
|
+
/** Field-of-view change at this focus and at minimum focus, beyond thin-lens unit focusing (fraction, negative = narrower). */
|
|
102
|
+
breathing: { fieldOfViewChange: number; atMinimumFocus: number };
|
|
103
|
+
/** Standard deviation of the autofocus error as a fraction of the depth of focus 2·N·c. */
|
|
104
|
+
focusScatterDepthOfFocusFraction: number;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## GenericLensCharacterValues
|
|
109
|
+
|
|
110
|
+
The character of a set, at each lens's widest aperture. Aberrations are in waves at 550 nm at the frame corner.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
export interface GenericLensCharacterValues {
|
|
114
|
+
/** Spherical aberration W040 (balanced by W020 = −W040): centre sharpness. */
|
|
115
|
+
sphericalWaves: number;
|
|
116
|
+
/** Coma W131 at H = 1: the comet-shaped point images of night photography. */
|
|
117
|
+
comaWaves: number;
|
|
118
|
+
/** Astigmatism W222 at H = 1: edge and corner sharpness. */
|
|
119
|
+
astigmatismWaves: number;
|
|
120
|
+
/** Field curvature W220 at H = 1: edge and corner sharpness. */
|
|
121
|
+
fieldCurvatureWaves: number;
|
|
122
|
+
/** Axial colour: the F-to-C difference of the defocus W020, waves at 550 nm. */
|
|
123
|
+
axialColorWaves: number;
|
|
124
|
+
/** Lateral colour: the F-to-C difference of the image radius at the corner, as a fraction of the half-diagonal. */
|
|
125
|
+
lateralColorFraction: number;
|
|
126
|
+
/** Corner distortion magnitude |k1| (fraction); each lens gives the sign (barrel −, pincushion +). */
|
|
127
|
+
distortionMagnitude: number;
|
|
128
|
+
/** Corner fall-off in stops that stays when stopped down (natural vignetting). */
|
|
129
|
+
naturalVignettingStops: number;
|
|
130
|
+
/** Extra corner fall-off in stops at the widest aperture (optical vignetting); gone two stops down. */
|
|
131
|
+
opticalVignettingStops: number;
|
|
132
|
+
/** Veiling glare: the fraction of the image's mean irradiance spread as a uniform veil. */
|
|
133
|
+
veilingGlareFraction: number;
|
|
134
|
+
/** Peak ghost power as a fraction of a bright source's power on the axis. */
|
|
135
|
+
ghostFraction: number;
|
|
136
|
+
/** Field-of-view change between infinity and minimum focus as a fraction (negative: narrower), beyond thin-lens unit focusing. */
|
|
137
|
+
breathingFraction: number;
|
|
138
|
+
/** Standard deviation of the autofocus error as a fraction of the depth of focus 2·N·c. */
|
|
139
|
+
focusScatterDepthOfFocusFraction: number;
|
|
140
|
+
/** c in the close-focus factor 1 + c·m/m_max. */
|
|
141
|
+
closeFocusFactor: number;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## GenericLensFocusError
|
|
146
|
+
|
|
147
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
export interface GenericLensFocusError {
|
|
151
|
+
lensId: string;
|
|
152
|
+
/** The intended and the sampled focus distance, m (the sampled one held within minimum focus and a far limit). */
|
|
153
|
+
intendedFocusDistanceM: number;
|
|
154
|
+
focusDistanceM: number;
|
|
155
|
+
/** The standard normal draw z and the image-side focus error z·σ·2Nc, µm (positive: a longer image distance, so focused nearer). */
|
|
156
|
+
standardNormal: number;
|
|
157
|
+
imageSideErrorMicrometers: number;
|
|
158
|
+
/** σ (fraction of the depth of focus) and the depth of focus 2·N·c, µm. */
|
|
159
|
+
depthOfFocusFraction: number;
|
|
160
|
+
depthOfFocusMicrometers: number;
|
|
161
|
+
/** True when the sampled image distance was held at the minimum-focus or the far (1000 m) limit. */
|
|
162
|
+
held: "minimum-focus" | "far-limit" | null;
|
|
163
|
+
scientificStatus: "approximation";
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## GenericLensKind
|
|
168
|
+
|
|
169
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
export type GenericLensKind = "standard" | "macro" | "fisheye";
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## GenericLensSet
|
|
176
|
+
|
|
177
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
export type GenericLensSet = "professional" | "consumer";
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## ResolvedGenericLens
|
|
184
|
+
|
|
185
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
export interface ResolvedGenericLens {
|
|
189
|
+
lensId: string;
|
|
190
|
+
name: string;
|
|
191
|
+
set: GenericLensSet;
|
|
192
|
+
kind: GenericLensKind;
|
|
193
|
+
equivalentFocalLengthMm: number;
|
|
194
|
+
/** Physical focal length on this sensor: equivalent ÷ crop factor. */
|
|
195
|
+
focalLengthMm: number;
|
|
196
|
+
cropFactor35Mm: number;
|
|
197
|
+
/** Half-diagonal of the sensor, mm: field height H = 1 and ρ = 1 of the profiles. */
|
|
198
|
+
halfDiagonalMm: number;
|
|
199
|
+
widestFNumber: number;
|
|
200
|
+
narrowestFNumber: number;
|
|
201
|
+
/** Marked third-stop f-numbers within the range. */
|
|
202
|
+
fNumbers: readonly number[];
|
|
203
|
+
/** max(0.10 m, f·(1 + 1/m_max)), metres from the lens (thin lens). */
|
|
204
|
+
minimumFocusDistanceM: number;
|
|
205
|
+
maximumMagnification: number;
|
|
206
|
+
projection: LensProjectionModel;
|
|
207
|
+
fisheyeImage: "circular" | "full-frame" | null;
|
|
208
|
+
/** Fisheyes: the radius of the 180° image circle on this sensor, 2·f·sin 45° (equisolid), mm. Null otherwise. */
|
|
209
|
+
imageCircleRadiusMm: number | null;
|
|
210
|
+
character: GenericLensCharacter;
|
|
211
|
+
note: string;
|
|
212
|
+
/** The catalogue entry's `summary`: a short plain-English description of the lens's character (at most 40 characters). */
|
|
213
|
+
summary: string;
|
|
214
|
+
evidence: readonly EvidenceProvenance[];
|
|
215
|
+
scientificStatus: "approximation";
|
|
216
|
+
limitations: readonly string[];
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## resolveGenericLens
|
|
221
|
+
|
|
222
|
+
Resolves a catalogue lens for a sensor: its focal length (equivalent ÷ the diagonal crop factor), f-number range, close focus
|
|
223
|
+
and projection, and its character (aberrations, colour, distortion, vignetting, flare, breathing and focus scatter) at the
|
|
224
|
+
given f-number and focus distance. Every value is a labelled approximation of a generic lens, not a measured product.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
export function resolveGenericLens(input: ResolveGenericLensInput): ResolvedGenericLens;
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
This call is synchronous; any supplied provider must follow its explicit synchronous contract.
|
|
231
|
+
|
|
232
|
+
## ResolveGenericLensInput
|
|
233
|
+
|
|
234
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
export interface ResolveGenericLensInput {
|
|
238
|
+
/** A catalogue id (`GENERIC_LENS_IDS`). */
|
|
239
|
+
lensId: string;
|
|
240
|
+
/** The sensor's active capture area, mm. */
|
|
241
|
+
activeImagingArea: { widthMm: number; heightMm: number };
|
|
242
|
+
/** Default: the widest aperture. Must lie in the lens's range. */
|
|
243
|
+
fNumber?: number;
|
|
244
|
+
/** Default: infinity. At least the minimum focus distance. */
|
|
245
|
+
focusDistanceM?: number;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## simulateGenericLensFocusError
|
|
250
|
+
|
|
251
|
+
Focus consistency (#549): a seeded, reproducible autofocus error for one shot. The image-side error is z·σ·2Nc, with z a
|
|
252
|
+
standard normal draw from (seed, shotIndex) (MurmurHash3 stream seed, xorshift32 and Box–Muller, the generators of the
|
|
253
|
+
capture's sensor noise), σ the lens's focus scatter as a fraction of the depth of focus 2·N·c, and
|
|
254
|
+
c the circle of confusion. The thin-lens image distance of the intended focus moves by that error and the focus distance is
|
|
255
|
+
read back from it. An approximation of autofocus variation; off unless called (or `simulateCapture`'s `lens.focusScatter`).
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
export function simulateGenericLensFocusError(input: SimulateGenericLensFocusErrorInput): GenericLensFocusError;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
This call is synchronous; any supplied provider must follow its explicit synchronous contract.
|
|
262
|
+
|
|
263
|
+
## SimulateGenericLensFocusErrorInput
|
|
264
|
+
|
|
265
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
export interface SimulateGenericLensFocusErrorInput {
|
|
269
|
+
lensId: string;
|
|
270
|
+
activeImagingArea: { widthMm: number; heightMm: number };
|
|
271
|
+
fNumber: number;
|
|
272
|
+
/** The intended focus distance, m. */
|
|
273
|
+
focusDistanceM: number;
|
|
274
|
+
/** uint32 seed of the shooting session. */
|
|
275
|
+
seed: number;
|
|
276
|
+
/** uint32 index of the shot in the session. Default 0. */
|
|
277
|
+
shotIndex?: number;
|
|
278
|
+
/** Circle of confusion for the depth of focus, mm. Default: the sensor diagonal / 1500. */
|
|
279
|
+
circleOfConfusionMm?: number;
|
|
280
|
+
}
|
|
281
|
+
```
|