@photivra/engine 2.15.0 → 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.
Files changed (67) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +14 -0
  6. package/dist/capture/native-environment-lean.js +1 -1
  7. package/dist/capture/native-environment-lean.js.map +1 -1
  8. package/dist/capture/native-raw-development.d.ts +15 -0
  9. package/dist/capture/native-raw-development.js +12 -6
  10. package/dist/capture/native-raw-development.js.map +1 -1
  11. package/dist/capture/raw-development-kernel.d.ts +10 -3
  12. package/dist/capture/raw-development-kernel.js +25 -6
  13. package/dist/capture/raw-development-kernel.js.map +1 -1
  14. package/dist/core/internal-radiance-evaluator.d.ts +5 -2
  15. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  16. package/dist/core/version.d.ts +1 -1
  17. package/dist/core/version.js +1 -1
  18. package/dist/core/version.js.map +1 -1
  19. package/dist/facade/defocus-image-space-provider.d.ts +109 -0
  20. package/dist/facade/defocus-image-space-provider.js +359 -0
  21. package/dist/facade/defocus-image-space-provider.js.map +1 -0
  22. package/dist/facade/diffraction-provider.d.ts +16 -0
  23. package/dist/facade/diffraction-provider.js +3 -3
  24. package/dist/facade/diffraction-provider.js.map +1 -1
  25. package/dist/facade/display-view.d.ts +21 -0
  26. package/dist/facade/display-view.js +66 -8
  27. package/dist/facade/display-view.js.map +1 -1
  28. package/dist/facade/simulate-capture.d.ts +111 -2
  29. package/dist/facade/simulate-capture.js +220 -44
  30. package/dist/facade/simulate-capture.js.map +1 -1
  31. package/dist/facade/simulate-display-view.js +7 -3
  32. package/dist/facade/simulate-display-view.js.map +1 -1
  33. package/dist/gpu/display-view-quality-gpu.d.ts +2 -0
  34. package/dist/gpu/display-view-quality-gpu.js +57 -13
  35. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  36. package/dist/gpu/display-view-speed-gpu.d.ts +2 -0
  37. package/dist/gpu/display-view-speed-gpu.js +45 -16
  38. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  39. package/dist/gpu/image-kernel-accelerator.js +3 -0
  40. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  41. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -0
  42. package/dist/gpu/wgsl-display-view-quality-kernel.js +53 -2
  43. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  44. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +15 -0
  45. package/dist/gpu/wgsl-display-view-speed-kernel.js +42 -1
  46. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index.js +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/optics/defocus-disc-kernel.d.ts +3 -2
  51. package/dist/optics/defocus-disc-kernel.js +3 -2
  52. package/dist/optics/defocus-disc-kernel.js.map +1 -1
  53. package/dist/optics/sensor-environment-query.d.ts +31 -0
  54. package/dist/optics/sensor-environment-query.js +45 -13
  55. package/dist/optics/sensor-environment-query.js.map +1 -1
  56. package/dist/processing/development-detail.d.ts +132 -0
  57. package/dist/processing/development-detail.js +325 -0
  58. package/dist/processing/development-detail.js.map +1 -0
  59. package/docs/API_REFERENCE.md +6 -1
  60. package/docs/QUICK_START.md +190 -12
  61. package/docs/USAGE.md +14 -3
  62. package/docs/api/capture-native-raw-development.md +16 -0
  63. package/docs/api/core-version.md +1 -1
  64. package/docs/api/exports.json +27 -2
  65. package/docs/api/facade-simulate-capture.md +128 -3
  66. package/docs/api/optics-defocus-disc-kernel.md +3 -2
  67. package/package.json +1 -1
@@ -219,6 +219,58 @@ export type SimulateCapturePixelAperture = "point" | "box" | {
219
219
  };
220
220
  /** Default midpoint samples per axis of `sensor.pixelAperture: "box"`. */
221
221
  export declare const SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES = 4;
222
+ /**
223
+ * `output.sharpening` (#552): unsharp mask y = x + amount·(x − G_σ∗x) where |x − G_σ∗x| ≥ threshold (GIMP and ImageMagick
224
+ * unsharp mask), per channel on sRGB-encoded values in [0, 1], then clipped to [0, 1]. Every field is optional.
225
+ */
226
+ export interface SimulateCaptureSharpening {
227
+ /** Gaussian standard deviation σ in output pixels, (0, 8]; the kernel reaches ⌈3σ⌉. Default 1. */
228
+ radiusPixels?: number;
229
+ /** Gain on the detail signal, (0, 5]. Default 0.5. */
230
+ amount?: number;
231
+ /** Detail smaller than this (encoded units, [0, 1]) is left unchanged. Default 0 (every difference is sharpened). */
232
+ threshold?: number;
233
+ }
234
+ /**
235
+ * `output.diffractionCorrection` (#552): the Wiener–Tikhonov gain G(s) = min(maximumGain, H(s)(1 + α)/(H(s)² + α)) on the circular-pupil
236
+ * OTF H at s = f/f_c, f_c = 1/(λ·N_w), as a (2R + 1)² FIR per channel. Every field is optional.
237
+ */
238
+ export interface SimulateCaptureDiffractionCorrection {
239
+ /** Design wavelength in nm, 380–780, used for all three channels. Default 550. */
240
+ wavelengthNm?: number;
241
+ /** α, an assumed noise-to-signal power ratio, 1e-4–1. Larger is safer and restores less. Default 0.01. */
242
+ regularization?: number;
243
+ /** Upper bound on the gain at any frequency, 1–10 (it bounds the noise amplification). Default 4. */
244
+ maximumGain?: number;
245
+ /** FIR radius R in pixels, 1–16. Default: the Airy pattern's third dark ring at the design wavelength, within 1–16. */
246
+ kernelRadiusPixels?: number;
247
+ }
248
+ /** `diagnostics.detail` (#552): what the development detail stages did. Present only when one ran. */
249
+ export interface SimulateCaptureDetailDiagnostics {
250
+ diffractionCorrection?: {
251
+ /** Corrected linear samples below 0 (ringing below black; clipped later by gamut handling) and above the RAW white level 1. */
252
+ negativeSampleCount: number;
253
+ aboveWhiteSampleCount: number;
254
+ /** The diffraction cut-off p/(λ·N_w) in cycles per pixel (Nyquist is 0.5); nothing beyond it is recovered. */
255
+ cutoffCyclesPerPixel: number;
256
+ /** Measured largest |response| of the filter, and largest |response| beyond the cut-off (null when the Nyquist square is inside it). */
257
+ responsePeakGain: number;
258
+ responseMaximumBeyondCutoff: number | null;
259
+ };
260
+ sharpening?: {
261
+ /** Half-width ⌈3σ⌉ of the Gaussian in pixels. */
262
+ kernelRadiusPixels: number;
263
+ /** Samples changed by the mask, and samples left alone because their detail was under the threshold. */
264
+ sharpenedSampleCount: number;
265
+ thresholdHeldSampleCount: number;
266
+ /** Sharpened samples clipped at 0 or 1. */
267
+ clippedLowSampleCount: number;
268
+ clippedHighSampleCount: number;
269
+ /** Largest increase and decrease amount·detail before clipping, in encoded units: the halo strength. */
270
+ maximumIncrease: number;
271
+ maximumDecrease: number;
272
+ };
273
+ }
222
274
  export interface SimulateCaptureMotion {
223
275
  /** Constant camera angular velocity, rad/s, in the engine's pitch/yaw/roll convention. Missing axes are 0. */
224
276
  angularVelocityRadPerSec?: {
@@ -370,6 +422,16 @@ export interface SimulateCaptureOptions {
370
422
  toneCurveParameters?: SdrToneCurveParameters;
371
423
  /** Default "clip". */
372
424
  highlightMode?: RawHighlightMode;
425
+ /**
426
+ * Opt-in (#556) development exposure push in stops, −2 to +5 (`NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS`); default 0.
427
+ * A linear gain 2^stops on the RAW after black subtraction and white normalization, before demosaic, white balance,
428
+ * the highlight mode, colour, the tone curve and encoding, like a raw editor's exposure slider ("brighten in development").
429
+ * A site pushed to or past white clips there and counts as saturated (`highlightMode: "reconstruct"` rolls it off);
430
+ * raised shadows keep their real noise and ADC quantization. The RAW codes, `result.raw` and a DNG are not changed;
431
+ * the developed samples (`image.samples`, also with format "dng"), JPEG, PNG, the ideal reference and the display view
432
+ * honour it. Reported in the assumption `output.exposureCompensationStops`. 0 or omitted: byte-identical to before.
433
+ */
434
+ exposureCompensationStops?: number;
373
435
  /** Default "scene-illuminant". */
374
436
  whiteBalance?: SimulateCaptureWhiteBalance;
375
437
  /** JPEG constant quantization step 1-255 (not a manufacturer 'quality'). Default 2. */
@@ -420,6 +482,20 @@ export interface SimulateCaptureOptions {
420
482
  width: number;
421
483
  height: number;
422
484
  };
485
+ /**
486
+ * Opt-in (#552) diffraction correction: a bounded, regularized deconvolution of the developed linear colour with the
487
+ * diffraction OTF of the ideal circular pupil at the capture's working f-number (see {@link SimulateCaptureDiffractionCorrection}).
488
+ * It runs after demosaic, colour and white balance and before the tone curve. Frequencies beyond the diffraction cut-off
489
+ * 1/(λ·N_w) never reached the sensor and stay lost: the correction's design gain there is 0. Needs `lens.diffraction`.
490
+ * Not combinable with `region`. The DNG's RAW codes are not changed. Default off.
491
+ */
492
+ diffractionCorrection?: SimulateCaptureDiffractionCorrection;
493
+ /**
494
+ * Opt-in (#552) unsharp mask (see {@link SimulateCaptureSharpening}) on the sRGB-encoded values after the tone curve and gamut
495
+ * handling, before quantization. It raises edge contrast and creates halos; it adds no optical detail. sRGB output only
496
+ * (not the wide-gamut or perceptual PNG paths); not combinable with `region`. The DNG's RAW codes are not changed. Default off.
497
+ */
498
+ sharpening?: SimulateCaptureSharpening;
423
499
  };
424
500
  /** Camera motion and shutter type. Default: still camera, global electronic shutter. */
425
501
  motion?: SimulateCaptureMotion;
@@ -465,7 +541,7 @@ export interface SimulateCaptureOptions {
465
541
  * Options reported in `diagnostics.options` (2.1), in this order, when they ran: the ones that change how much work
466
542
  * a capture does. `motion.rotation` is a non-zero angular velocity; `sensor.temporalSampleCount` a count above 1.
467
543
  */
468
- export declare const SIMULATE_CAPTURE_COST_OPTIONS: readonly ["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf", "lens.diffraction", "lens.defocus", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region", "output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual", "output.format.jpeg", "output.format.png", "output.format.dng"];
544
+ export declare const SIMULATE_CAPTURE_COST_OPTIONS: readonly ["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf", "lens.diffraction", "lens.defocus", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region", "output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual", "output.diffractionCorrection", "output.sharpening", "output.format.jpeg", "output.format.png", "output.format.dng"];
469
545
  export type SimulateCaptureCostOption = typeof SIMULATE_CAPTURE_COST_OPTIONS[number];
470
546
  /**
471
547
  * The options that take the exact-or-fast flag (#395), in the path style of {@link SIMULATE_CAPTURE_COST_OPTIONS}: each accepts
@@ -558,7 +634,7 @@ export interface SimulateCaptureResult {
558
634
  psf: Extract<CalculateEnvironmentSensorPhotoSignalInput["psf"], {
559
635
  kind: "sampled-local";
560
636
  }>;
561
- execution: "object-space-convolution" | "environment-psf-quadrature";
637
+ execution: "object-space-convolution" | "environment-psf-quadrature" | "image-space-convolution";
562
638
  };
563
639
  };
564
640
  exposure: {
@@ -655,6 +731,8 @@ export interface SimulateCaptureResult {
655
731
  adjustedPixelCount: number;
656
732
  lightnessClippedPixelCount: number;
657
733
  };
734
+ /** #552: present with output.diffractionCorrection or output.sharpening. */
735
+ detail?: SimulateCaptureDetailDiagnostics;
658
736
  /**
659
737
  * Wall-clock milliseconds per stage. `raw`, `whiteBalance` (the second RAW pass of auto white balance), `development`
660
738
  * (includes DNG writing), `jpeg` and `total` are whole milliseconds (`Date.now()`). Since 2.1, where a stage ran:
@@ -915,6 +993,8 @@ export interface DisplayViewResult {
915
993
  };
916
994
  /** #395: exact or fast, as requested and as run; 2.11 (#481): plus `execution`, where the render ran (see {@link DisplayViewFastReport}). */
917
995
  fast: DisplayViewFastReport;
996
+ /** #533: the saturated (clipped highlight) pixels of the real capture in the rendered region, as an expected value (see {@link DisplayViewHighlightDiagnostics}). */
997
+ highlight: DisplayViewHighlightDiagnostics;
918
998
  };
919
999
  provenance: {
920
1000
  scientificStatus: "approximation";
@@ -927,6 +1007,33 @@ export interface DisplayViewResult {
927
1007
  /** The capture request's assumptions, then `view.definition`, `view.estimation`, `view.noise`, `view.demosaic` and `view.source`. */
928
1008
  assumptions: readonly SimulateCaptureAssumption[];
929
1009
  }
1010
+ /**
1011
+ * #533: the display view's highlight count. `simulateCapture` reports `diagnostics.highlight.saturatedPixelCount`, the
1012
+ * output pixels with at least one clipped contributing RAW site (a site flagged saturated or at the white level) counted on its
1013
+ * one noise sample (`sensor.noiseSeed`). The display view renders no RAW, so it reports that count's expected value over the
1014
+ * 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,
1015
+ * 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
1016
+ * normalized value normal with its exact expected electrons, shot, dark, read and ADC noise, as the expected value uses), that at
1017
+ * least one site pixel j's demosaic reads is at or above the white level, the sites' noises independent. It is estimated as the
1018
+ * view's expected value is (`view.estimation`: exact over every real pixel, or the stratified samples), so it is not an integer.
1019
+ *
1020
+ * - Where every contributing site is far from white (more than 8.5 noise standard deviations either side) P_j is exactly 0 or 1,
1021
+ * and the expected count equals the capture's count of the same pixels exactly (they then have no noise to differ by).
1022
+ * - Regions are additive: the counts of regions that tile the view sum to the whole view's count (up to floating-point summation
1023
+ * order), each display pixel's share being the same in every region that renders it.
1024
+ * - With the bilinear demosaic the sites are the capture's own contributing sites. With AHD the capture counts every site within
1025
+ * 3 sites of the pixel (its interpolation reach, clipped to the raster); the view counts the 25–29 of those 49 that its AHD
1026
+ * candidates read, so its count can be lower where a clipped area ends in a corner or a speck 2–3 sites away diagonally
1027
+ * (an approximation; along a straight clipped edge the two agree).
1028
+ * - It never changes a sample of the view. On the GPU (`prepareDisplayView` with `gpu`) it is the CPU reference's value: the
1029
+ * kernel decides P_j only where it is certainly 0 or 1 and the host computes every other pixel's share with the reference.
1030
+ */
1031
+ export interface DisplayViewHighlightDiagnostics {
1032
+ /** Expected number of saturated real output pixels in the rendered region (a non-negative real number; see above). */
1033
+ saturatedPixelCount: number;
1034
+ /** The statistic: always "expected-value" (the capture's own count is one noise sample of it). */
1035
+ statistic: "expected-value";
1036
+ }
930
1037
  /** Internal: the resolved request state that the display view's renderer (simulate-display-view.ts) reads. */
931
1038
  export interface DisplayViewRunContext {
932
1039
  ahd: boolean;
@@ -976,6 +1083,8 @@ export interface DisplayViewRunContext {
976
1083
  focusDistanceM: number;
977
1084
  height: number;
978
1085
  highlightMode: RawHighlightMode;
1086
+ /** #556: the development exposure gain 2^output.exposureCompensationStops (1 without a push). */
1087
+ exposureGain: number;
979
1088
  illuminanceLux: number;
980
1089
  illuminant: ResolvedCaptureIlluminant;
981
1090
  illuminantOption: CaptureIlluminant;