@photivra/engine 2.14.1 → 2.16.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 +69 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +14 -0
- 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/facade/defocus-image-space-provider.d.ts +109 -0
- package/dist/facade/defocus-image-space-provider.js +359 -0
- package/dist/facade/defocus-image-space-provider.js.map +1 -0
- package/dist/facade/diffraction-provider.d.ts +16 -0
- package/dist/facade/diffraction-provider.js +3 -3
- package/dist/facade/diffraction-provider.js.map +1 -1
- 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/simulate-capture.d.ts +112 -3
- package/dist/facade/simulate-capture.js +235 -48
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +7 -3
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +2 -0
- package/dist/gpu/display-view-quality-gpu.js +57 -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 +45 -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 +3 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/optics/defocus-disc-kernel.d.ts +91 -1
- package/dist/optics/defocus-disc-kernel.js +59 -5
- package/dist/optics/defocus-disc-kernel.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.d.ts +9 -4
- package/dist/optics/diffraction-psf-profile.js +14 -6
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/dist/optics/lens-psf-kernels.d.ts +13 -2
- package/dist/optics/lens-psf-kernels.js +23 -6
- package/dist/optics/lens-psf-kernels.js.map +1 -1
- package/dist/optics/sensor-environment-query.d.ts +31 -0
- package/dist/optics/sensor-environment-query.js +45 -13
- package/dist/optics/sensor-environment-query.js.map +1 -1
- package/dist/optics/site-diffraction-kernel.d.ts +1 -1
- package/dist/optics/site-diffraction-kernel.js +37 -17
- package/dist/optics/site-diffraction-kernel.js.map +1 -1
- 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 +17 -1
- package/docs/QUICK_START.md +210 -16
- package/docs/USAGE.md +31 -3
- package/docs/api/capture-native-raw-development.md +16 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +47 -2
- package/docs/api/facade-simulate-capture.md +129 -4
- package/docs/api/optics-defocus-disc-kernel.md +87 -0
- package/docs/api/optics-diffraction-psf-profile.md +6 -3
- package/docs/api/optics-lens-psf-kernels.md +3 -2
- package/llms.txt +1 -0
- package/package.json +1 -1
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
+
## DisplayViewHighlightDiagnostics
|
|
6
|
+
|
|
7
|
+
#533: the display view's highlight count. `simulateCapture` reports `diagnostics.highlight.saturatedPixelCount`, the
|
|
8
|
+
output pixels with at least one clipped contributing RAW site (a site flagged saturated or at the white level) counted on its
|
|
9
|
+
one noise sample (`sensor.noiseSeed`). The display view renders no RAW, so it reports that count's expected value over the
|
|
10
|
+
noise: Σ_j w_j·P_j over the real output pixels j of the rendered region, w_j the part of pixel j inside the region (1 inside,
|
|
11
|
+
a fraction at a region edge that cuts a real pixel) and P_j the probability, under the view's own site model (each site's
|
|
12
|
+
normalized value normal with its exact expected electrons, shot, dark, read and ADC noise, as the expected value uses), that at
|
|
13
|
+
least one site pixel j's demosaic reads is at or above the white level, the sites' noises independent. It is estimated as the
|
|
14
|
+
view's expected value is (`view.estimation`: exact over every real pixel, or the stratified samples), so it is not an integer.
|
|
15
|
+
|
|
16
|
+
- Where every contributing site is far from white (more than 8.5 noise standard deviations either side) P_j is exactly 0 or 1,
|
|
17
|
+
and the expected count equals the capture's count of the same pixels exactly (they then have no noise to differ by).
|
|
18
|
+
- Regions are additive: the counts of regions that tile the view sum to the whole view's count (up to floating-point summation
|
|
19
|
+
order), each display pixel's share being the same in every region that renders it.
|
|
20
|
+
- With the bilinear demosaic the sites are the capture's own contributing sites. With AHD the capture counts every site within
|
|
21
|
+
3 sites of the pixel (its interpolation reach, clipped to the raster); the view counts the 25–29 of those 49 that its AHD
|
|
22
|
+
candidates read, so its count can be lower where a clipped area ends in a corner or a speck 2–3 sites away diagonally
|
|
23
|
+
(an approximation; along a straight clipped edge the two agree).
|
|
24
|
+
- It never changes a sample of the view. On the GPU (`prepareDisplayView` with `gpu`) it is the CPU reference's value: the
|
|
25
|
+
kernel decides P_j only where it is certainly 0 or 1 and the host computes every other pixel's share with the reference.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
export interface DisplayViewHighlightDiagnostics {
|
|
29
|
+
/** Expected number of saturated real output pixels in the rendered region (a non-negative real number; see above). */
|
|
30
|
+
saturatedPixelCount: number;
|
|
31
|
+
/** The statistic: always "expected-value" (the capture's own count is one noise sample of it). */
|
|
32
|
+
statistic: "expected-value";
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
5
36
|
## DisplayViewResult
|
|
6
37
|
|
|
7
38
|
Result of {@link simulateDisplayView }: the full-resolution capture resized for display, estimated; not a capture.
|
|
@@ -43,7 +74,9 @@ export interface DisplayViewResult {
|
|
|
43
74
|
diagnostics: { sampleCount: number; siteEvaluations: number; sceneLookups: number; detailPixelCount: number; uniformPixelCount: number; ahdCorrectedPixelCount: number;
|
|
44
75
|
timingsMs: { view: number; total: number };
|
|
45
76
|
/** #395: exact or fast, as requested and as run; 2.11 (#481): plus `execution`, where the render ran (see {@link DisplayViewFastReport}). */
|
|
46
|
-
fast: DisplayViewFastReport
|
|
77
|
+
fast: DisplayViewFastReport;
|
|
78
|
+
/** #533: the saturated (clipped highlight) pixels of the real capture in the rendered region, as an expected value (see {@link DisplayViewHighlightDiagnostics}). */
|
|
79
|
+
highlight: DisplayViewHighlightDiagnostics };
|
|
47
80
|
provenance: { scientificStatus: "approximation"; cameraPresetId: string; cameraPresetVersion: string; evidence: readonly EvidenceProvenance[];
|
|
48
81
|
composedContracts: readonly string[]; limitations: readonly string[] };
|
|
49
82
|
/** The capture request's assumptions, then `view.definition`, `view.estimation`, `view.noise`, `view.demosaic` and `view.source`. */
|
|
@@ -111,7 +144,7 @@ a capture does. `motion.rotation` is a non-zero angular velocity; `sensor.tempor
|
|
|
111
144
|
SIMULATE_CAPTURE_COST_OPTIONS = Object.freeze(["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf",
|
|
112
145
|
"lens.diffraction", "lens.defocus", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region",
|
|
113
146
|
"output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual",
|
|
114
|
-
"output.format.jpeg", "output.format.png", "output.format.dng"] as const)
|
|
147
|
+
"output.diffractionCorrection", "output.sharpening", "output.format.jpeg", "output.format.png", "output.format.dng"] as const)
|
|
115
148
|
```
|
|
116
149
|
|
|
117
150
|
## SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX
|
|
@@ -334,6 +367,56 @@ export type SimulateCaptureDemosaic = "bilinear" | "ahd";
|
|
|
334
367
|
export interface SimulateCaptureDemosaicRecord { kind: SimulateCaptureDemosaic; fast?: boolean }
|
|
335
368
|
```
|
|
336
369
|
|
|
370
|
+
## SimulateCaptureDetailDiagnostics
|
|
371
|
+
|
|
372
|
+
`diagnostics.detail` (#552): what the development detail stages did. Present only when one ran.
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
export interface SimulateCaptureDetailDiagnostics {
|
|
376
|
+
diffractionCorrection?: {
|
|
377
|
+
/** Corrected linear samples below 0 (ringing below black; clipped later by gamut handling) and above the RAW white level 1. */
|
|
378
|
+
negativeSampleCount: number;
|
|
379
|
+
aboveWhiteSampleCount: number;
|
|
380
|
+
/** The diffraction cut-off p/(λ·N_w) in cycles per pixel (Nyquist is 0.5); nothing beyond it is recovered. */
|
|
381
|
+
cutoffCyclesPerPixel: number;
|
|
382
|
+
/** Measured largest |response| of the filter, and largest |response| beyond the cut-off (null when the Nyquist square is inside it). */
|
|
383
|
+
responsePeakGain: number;
|
|
384
|
+
responseMaximumBeyondCutoff: number | null;
|
|
385
|
+
};
|
|
386
|
+
sharpening?: {
|
|
387
|
+
/** Half-width ⌈3σ⌉ of the Gaussian in pixels. */
|
|
388
|
+
kernelRadiusPixels: number;
|
|
389
|
+
/** Samples changed by the mask, and samples left alone because their detail was under the threshold. */
|
|
390
|
+
sharpenedSampleCount: number;
|
|
391
|
+
thresholdHeldSampleCount: number;
|
|
392
|
+
/** Sharpened samples clipped at 0 or 1. */
|
|
393
|
+
clippedLowSampleCount: number;
|
|
394
|
+
clippedHighSampleCount: number;
|
|
395
|
+
/** Largest increase and decrease amount·detail before clipping, in encoded units: the halo strength. */
|
|
396
|
+
maximumIncrease: number;
|
|
397
|
+
maximumDecrease: number;
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## SimulateCaptureDiffractionCorrection
|
|
403
|
+
|
|
404
|
+
`output.diffractionCorrection` (#552): the Wiener–Tikhonov gain G(s) = min(maximumGain, H(s)(1 + α)/(H(s)² + α)) on the circular-pupil
|
|
405
|
+
OTF H at s = f/f_c, f_c = 1/(λ·N_w), as a (2R + 1)² FIR per channel. Every field is optional.
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
export interface SimulateCaptureDiffractionCorrection {
|
|
409
|
+
/** Design wavelength in nm, 380–780, used for all three channels. Default 550. */
|
|
410
|
+
wavelengthNm?: number;
|
|
411
|
+
/** α, an assumed noise-to-signal power ratio, 1e-4–1. Larger is safer and restores less. Default 0.01. */
|
|
412
|
+
regularization?: number;
|
|
413
|
+
/** Upper bound on the gain at any frequency, 1–10 (it bounds the noise amplification). Default 4. */
|
|
414
|
+
maximumGain?: number;
|
|
415
|
+
/** FIR radius R in pixels, 1–16. Default: the Airy pattern's third dark ring at the design wavelength, within 1–16. */
|
|
416
|
+
kernelRadiusPixels?: number;
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
337
420
|
## SimulateCaptureDngPart
|
|
338
421
|
|
|
339
422
|
One part of a whole-frame DNG (#391): a region render's packed RAW and where its native sites lie (`result.raw`, `result.region`).
|
|
@@ -544,6 +627,16 @@ export interface SimulateCaptureOptions {
|
|
|
544
627
|
toneCurveParameters?: SdrToneCurveParameters;
|
|
545
628
|
/** Default "clip". */
|
|
546
629
|
highlightMode?: RawHighlightMode;
|
|
630
|
+
/**
|
|
631
|
+
* Opt-in (#556) development exposure push in stops, −2 to +5 (`NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS`); default 0.
|
|
632
|
+
* A linear gain 2^stops on the RAW after black subtraction and white normalization, before demosaic, white balance,
|
|
633
|
+
* the highlight mode, colour, the tone curve and encoding, like a raw editor's exposure slider ("brighten in development").
|
|
634
|
+
* A site pushed to or past white clips there and counts as saturated (`highlightMode: "reconstruct"` rolls it off);
|
|
635
|
+
* raised shadows keep their real noise and ADC quantization. The RAW codes, `result.raw` and a DNG are not changed;
|
|
636
|
+
* the developed samples (`image.samples`, also with format "dng"), JPEG, PNG, the ideal reference and the display view
|
|
637
|
+
* honour it. Reported in the assumption `output.exposureCompensationStops`. 0 or omitted: byte-identical to before.
|
|
638
|
+
*/
|
|
639
|
+
exposureCompensationStops?: number;
|
|
547
640
|
/** Default "scene-illuminant". */
|
|
548
641
|
whiteBalance?: SimulateCaptureWhiteBalance;
|
|
549
642
|
/** JPEG constant quantization step 1-255 (not a manufacturer 'quality'). Default 2. */
|
|
@@ -587,6 +680,20 @@ export interface SimulateCaptureOptions {
|
|
|
587
680
|
* as a capture of the region's size.
|
|
588
681
|
*/
|
|
589
682
|
region?: { x: number; y: number; width: number; height: number };
|
|
683
|
+
/**
|
|
684
|
+
* Opt-in (#552) diffraction correction: a bounded, regularized deconvolution of the developed linear colour with the
|
|
685
|
+
* diffraction OTF of the ideal circular pupil at the capture's working f-number (see {@link SimulateCaptureDiffractionCorrection}).
|
|
686
|
+
* It runs after demosaic, colour and white balance and before the tone curve. Frequencies beyond the diffraction cut-off
|
|
687
|
+
* 1/(λ·N_w) never reached the sensor and stay lost: the correction's design gain there is 0. Needs `lens.diffraction`.
|
|
688
|
+
* Not combinable with `region`. The DNG's RAW codes are not changed. Default off.
|
|
689
|
+
*/
|
|
690
|
+
diffractionCorrection?: SimulateCaptureDiffractionCorrection;
|
|
691
|
+
/**
|
|
692
|
+
* Opt-in (#552) unsharp mask (see {@link SimulateCaptureSharpening}) on the sRGB-encoded values after the tone curve and gamut
|
|
693
|
+
* handling, before quantization. It raises edge contrast and creates halos; it adds no optical detail. sRGB output only
|
|
694
|
+
* (not the wide-gamut or perceptual PNG paths); not combinable with `region`. The DNG's RAW codes are not changed. Default off.
|
|
695
|
+
*/
|
|
696
|
+
sharpening?: SimulateCaptureSharpening;
|
|
590
697
|
};
|
|
591
698
|
/** Camera motion and shutter type. Default: still camera, global electronic shutter. */
|
|
592
699
|
motion?: SimulateCaptureMotion;
|
|
@@ -708,7 +815,7 @@ export interface SimulateCaptureResult {
|
|
|
708
815
|
* scene provider gives the same expected signal (to float rounding) through the per-tap environment quadrature.
|
|
709
816
|
*/
|
|
710
817
|
diffraction?: { profile: DiffractionLimitedPsfProfile; psf: Extract<CalculateEnvironmentSensorPhotoSignalInput["psf"], { kind: "sampled-local" }>;
|
|
711
|
-
execution: "object-space-convolution" | "environment-psf-quadrature" };
|
|
818
|
+
execution: "object-space-convolution" | "environment-psf-quadrature" | "image-space-convolution" };
|
|
712
819
|
};
|
|
713
820
|
exposure: {
|
|
714
821
|
mode: SimulateCaptureExposureMode;
|
|
@@ -781,6 +888,8 @@ export interface SimulateCaptureResult {
|
|
|
781
888
|
rendering: SdrRenderingResult["diagnostics"];
|
|
782
889
|
/** Present with output.gamutMapping "perceptual": pixels whose chroma or lightness the mapping changed. */
|
|
783
890
|
gamutMapping?: { method: "cielab-hue-preserving-chroma-compression"; kneeFraction: number; adjustedPixelCount: number; lightnessClippedPixelCount: number };
|
|
891
|
+
/** #552: present with output.diffractionCorrection or output.sharpening. */
|
|
892
|
+
detail?: SimulateCaptureDetailDiagnostics;
|
|
784
893
|
/**
|
|
785
894
|
* Wall-clock milliseconds per stage. `raw`, `whiteBalance` (the second RAW pass of auto white balance), `development`
|
|
786
895
|
* (includes DNG writing), `jpeg` and `total` are whole milliseconds (`Date.now()`). Since 2.1, where a stage ran:
|
|
@@ -831,6 +940,22 @@ export type SimulateCaptureSensorAntiAliasing =
|
|
|
831
940
|
| { kind: "point-splitting"; components: readonly AntiAliasingPointSplitComponent[]; fast?: boolean };
|
|
832
941
|
```
|
|
833
942
|
|
|
943
|
+
## SimulateCaptureSharpening
|
|
944
|
+
|
|
945
|
+
`output.sharpening` (#552): unsharp mask y = x + amount·(x − G_σ∗x) where |x − G_σ∗x| ≥ threshold (GIMP and ImageMagick
|
|
946
|
+
unsharp mask), per channel on sRGB-encoded values in [0, 1], then clipped to [0, 1]. Every field is optional.
|
|
947
|
+
|
|
948
|
+
```ts
|
|
949
|
+
export interface SimulateCaptureSharpening {
|
|
950
|
+
/** Gaussian standard deviation σ in output pixels, (0, 8]; the kernel reaches ⌈3σ⌉. Default 1. */
|
|
951
|
+
radiusPixels?: number;
|
|
952
|
+
/** Gain on the detail signal, (0, 5]. Default 0.5. */
|
|
953
|
+
amount?: number;
|
|
954
|
+
/** Detail smaller than this (encoded units, [0, 1]) is left unchanged. Default 0 (every difference is sharpened). */
|
|
955
|
+
threshold?: number;
|
|
956
|
+
}
|
|
957
|
+
```
|
|
958
|
+
|
|
834
959
|
## SimulateCaptureWhiteBalance
|
|
835
960
|
|
|
836
961
|
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
@@ -853,7 +978,7 @@ export interface SimulateDisplayViewOptions {
|
|
|
853
978
|
/** Display pixels, 1 to the capture's output width and height. */
|
|
854
979
|
width: number;
|
|
855
980
|
height: number;
|
|
856
|
-
/** Stratified Monte Carlo samples per axis for each display pixel's expected value (1–32, default
|
|
981
|
+
/** Stratified Monte Carlo samples per axis for each display pixel's expected value (1–32, default 16: 256 real pixels where the display pixel has detail). */
|
|
857
982
|
samplesPerAxis?: number;
|
|
858
983
|
/** "srgb-8" (default) or "srgb-16". */
|
|
859
984
|
format?: "srgb-8" | "srgb-16";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# optics/defocus-disc-kernel.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
|
+
## calculateDefocusDiscKernelRadius
|
|
6
|
+
|
|
7
|
+
Checks, before rendering, whether `lens.defocus: { model: "geometric-disc" }` can render a request: the geometric
|
|
8
|
+
defocus disc's diameter on the site lattice, the kernel radius it needs, and whether that radius is within
|
|
9
|
+
{@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (#532). Pure and cheap: no kernel is built and nothing is rendered.
|
|
10
|
+
|
|
11
|
+
It runs the same resolution and the same limit test as `simulateCapture`, `simulateDisplayView` and `prepareDisplayView`
|
|
12
|
+
(the blur circle c = f²·|s − d| / (N·d·(s − f)) of `calculateDefocusCircle`, thin-lens model 1.1.0, divided by the pitch),
|
|
13
|
+
so for the same focal length, f-number, distances and pitch a request they refuse for its defocus size has
|
|
14
|
+
`withinLimit: false`, and one they accept has `withinLimit: true`. Pass the values the capture uses: in an automatic
|
|
15
|
+
exposure mode that is the resolved f-number. The limit applies to the disc alone; with `lens.diffraction` the combined
|
|
16
|
+
kernel (diffraction radius plus defocus radius, at most 32 + 64 sites) has no further limit. Other reasons a capture
|
|
17
|
+
refuses `lens.defocus` out of focus (pupil sampling, a caller PSF, camera rotation with a rolling shutter, a vignetting or
|
|
18
|
+
distortion profile that does not reach the kernel's radius beyond the frame, and in the display view a projection,
|
|
19
|
+
distortion, vignetting or rotation) are not checked here.
|
|
20
|
+
|
|
21
|
+
The blur circle is a geometric thin-lens approximation for an ideal uniform circular pupil (no diffraction, aberrations
|
|
22
|
+
or pupil vignetting). Inputs follow `calculateDefocusCircle`: all positive and finite, both distances beyond the focal
|
|
23
|
+
length; invalid values fail with `InvalidScientificInputError`, unknown fields with `InvalidConfigurationError`.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
export function calculateDefocusDiscKernelRadius(input: CalculateDefocusDiscKernelRadiusInput): CalculationResult<DefocusDiscKernelRadius>;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This call is synchronous; any supplied provider must follow its explicit synchronous contract.
|
|
30
|
+
|
|
31
|
+
## CalculateDefocusDiscKernelRadiusInput
|
|
32
|
+
|
|
33
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
export interface CalculateDefocusDiscKernelRadiusInput {
|
|
37
|
+
/** Lens focal length in millimetres (`lens.focalLengthMm`). */
|
|
38
|
+
focalLengthMm: number;
|
|
39
|
+
/** F-number the capture uses: `lens.fNumber`, or in an automatic exposure mode the resolved `exposure.settings.fNumber`. */
|
|
40
|
+
aperture: number;
|
|
41
|
+
/** Focus-plane distance from the lens plane in metres (`lens.focusDistanceM`; `simulateCapture` defaults it to the subject distance). */
|
|
42
|
+
focusDistanceM: number;
|
|
43
|
+
/** Scene-plane distance from the lens plane in metres (`subjectDistanceM`). */
|
|
44
|
+
subjectDistanceM: number;
|
|
45
|
+
/** Sensor site pitch in micrometres (`sensor.pixelPitchMicrometers`), the lattice the disc is sampled on. */
|
|
46
|
+
pixelPitchMicrometers: number;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES
|
|
51
|
+
|
|
52
|
+
Largest kernel radius in sites of `lens.defocus: { model: "geometric-disc" }`, R ≤ 64: a disc of at most 129 sites
|
|
53
|
+
across (a 129 × 129 window, 16 641 taps per site; the diffraction kernels stop at 32). `simulateCapture`,
|
|
54
|
+
`simulateDisplayView` and `prepareDisplayView` refuse a request whose disc needs more, and never clip the disc; check a
|
|
55
|
+
request first with {@link calculateDefocusDiscKernelRadius}.
|
|
56
|
+
|
|
57
|
+
Set from the cost measured in the capture (#492, step 2a; docs/QUICK_START.md, "Optics"): the capture's time grows with
|
|
58
|
+
R² at every site the disc sees an edge from, and a 0.7 MP Siemens star took 0.37 s in focus, 2.9 s at R = 32, 4.7 s at
|
|
59
|
+
R = 41 and 11.6 s at R = 64 (Node, one thread). 64 keeps the issue's 1.4 µm example (50 mm f/2.8, subject 2 m, focus
|
|
60
|
+
1.6 m: R = 41) with room for larger blurs, at a cost the caller chooses and the docs state; past it the call fails.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES = 64
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## DefocusDiscKernelRadius
|
|
67
|
+
|
|
68
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
export interface DefocusDiscKernelRadius {
|
|
72
|
+
/** Geometric blur-circle diameter c at the sensor in micrometres (`calculateDefocusCircle`, thin-lens model 1.1.0); 0 in focus. */
|
|
73
|
+
diameterMicrometers: number;
|
|
74
|
+
/** c / pixel pitch: the disc's diameter in sites. */
|
|
75
|
+
diameterSites: number;
|
|
76
|
+
/** Kernel radius R in sites the renderer needs for this disc, ⌈c / (2·pitch) − ½⌉ (0 for a disc inside one site, and in focus). */
|
|
77
|
+
kernelRadiusSites: number;
|
|
78
|
+
/** {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, 64. */
|
|
79
|
+
maximumKernelRadiusSites: number;
|
|
80
|
+
/** Largest disc diameter in sites within the limit, 2·64 + 1 = 129: within the limit exactly when `diameterSites` ≤ 129. */
|
|
81
|
+
maximumDiameterSites: number;
|
|
82
|
+
/** True when `kernelRadiusSites` ≤ `maximumKernelRadiusSites`: `lens.defocus` renders the request; false: it refuses it. */
|
|
83
|
+
withinLimit: boolean;
|
|
84
|
+
/** How far past the limit the kernel radius is, max(0, R − 64) sites; 0 when within the limit. */
|
|
85
|
+
excessKernelRadiusSites: number;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
@@ -26,7 +26,7 @@ export interface CreateDiffractionLimitedPsfProfileInput {
|
|
|
26
26
|
focalLengthMm: number;
|
|
27
27
|
apertureFNumber: number;
|
|
28
28
|
focus: FocusPlane;
|
|
29
|
-
/** Strictly increasing wavelengths, nm, in the medium of propagation (no air/vacuum conversion is applied). 1–
|
|
29
|
+
/** Strictly increasing wavelengths, nm, in the medium of propagation (no air/vacuum conversion is applied). 1–400. */
|
|
30
30
|
wavelengthsNm: readonly number[];
|
|
31
31
|
/** Square sample pitch of every kernel, µm. */
|
|
32
32
|
samplePitchMicrometers: number;
|
|
@@ -40,10 +40,13 @@ export interface CreateDiffractionLimitedPsfProfileInput {
|
|
|
40
40
|
|
|
41
41
|
## DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS
|
|
42
42
|
|
|
43
|
-
Input limits: at most
|
|
43
|
+
Input limits: at most 400 wavelengths, kernel radius 0–32 samples (the irradiance-convolution limit). 400 is the finest spectral
|
|
44
|
+
sampling of `simulateCapture` and the display view (`sensor.spectralStepNanometers` 1 nm over 380–780 nm), so `lens.diffraction`
|
|
45
|
+
builds one exact kernel per spectral node at every allowed step; it was 64 before 2.15.0, which refused every step under 6.25 nm.
|
|
46
|
+
The work and memory are linear in the count (measured in docs/PSF_FOUNDATION.md, "Spectral nodes").
|
|
44
47
|
|
|
45
48
|
```ts
|
|
46
|
-
DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS = Object.freeze({ maximumWavelengths:
|
|
49
|
+
DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS = Object.freeze({ maximumWavelengths: 400, maximumKernelRadiusSamples: 32 })
|
|
47
50
|
```
|
|
48
51
|
|
|
49
52
|
## DIFFRACTION_LIMITED_PSF_PROFILE_MODEL
|
|
@@ -4,10 +4,11 @@ The package and root API versions are recorded once in [Navigation](../API_REFER
|
|
|
4
4
|
|
|
5
5
|
## LENS_PSF_CONVOLUTION_KERNELS_MAXIMUM_WAVELENGTHS
|
|
6
6
|
|
|
7
|
-
At most this many wavelengths per call
|
|
7
|
+
At most this many wavelengths per call: 400, the finest spectral sampling of `simulateCapture` (1 nm over 380–780 nm),
|
|
8
|
+
as `DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS.maximumWavelengths` (64 before 2.15.0). The work is linear in the count.
|
|
8
9
|
|
|
9
10
|
```ts
|
|
10
|
-
LENS_PSF_CONVOLUTION_KERNELS_MAXIMUM_WAVELENGTHS =
|
|
11
|
+
LENS_PSF_CONVOLUTION_KERNELS_MAXIMUM_WAVELENGTHS = 400
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
## LENS_PSF_CONVOLUTION_KERNELS_SCHEMA_VERSION
|
package/llms.txt
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- [calculateEquivalentIso()](https://github.com/Photivra/photivra/blob/main/docs/api/exposure-exposure.md#calculateequivalentiso): ISO that keeps the same brightness after an aperture or shutter change
|
|
21
21
|
- [calculateDepthOfField()](https://github.com/Photivra/photivra/blob/main/docs/api/optics-depth-of-field.md#calculatedepthoffield): near and far limits and hyperfocal distance (thin lens)
|
|
22
22
|
- [calculateDefocusCircle()](https://github.com/Photivra/photivra/blob/main/docs/api/optics-depth-of-field.md#calculatedefocuscircle): blur-circle size for an out-of-focus subject
|
|
23
|
+
- [calculateDefocusDiscKernelRadius()](https://github.com/Photivra/photivra/blob/main/docs/api/optics-defocus-disc-kernel.md#calculatedefocusdisckernelradius): whether `lens.defocus` can render a blur before you render it (the 64-site limit)
|
|
23
24
|
- [calculateFieldOfView()](https://github.com/Photivra/photivra/blob/main/docs/api/camera-field-of-view.md#calculatefieldofview): angle of view from focal length and sensor size
|
|
24
25
|
- [calculateEquivalentFocalLength35Mm()](https://github.com/Photivra/photivra/blob/main/docs/api/camera-equivalent-focal-length.md#calculateequivalentfocallength35mm): crop factor and 35 mm-equivalent focal length
|
|
25
26
|
- [calculateCenteredCrop()](https://github.com/Photivra/photivra/blob/main/docs/api/output-crop.md#calculatecenteredcrop): pixels left after a centred crop
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@photivra/engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Scientific TypeScript engine for camera optics, exposure, focus, motion, sensor sampling, diffraction, and digital imaging calculations.",
|
|
6
6
|
"type": "module",
|