@photivra/engine 2.19.0 → 2.20.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 (129) hide show
  1. package/CHANGELOG.md +112 -1
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +7 -5
  4. package/README.md +6 -6
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +14 -10
  7. package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
  8. package/dist/capture/dng-lens-correction-opcodes.js +119 -0
  9. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
  10. package/dist/capture/native-environment-lean.js +1 -5
  11. package/dist/capture/native-environment-lean.js.map +1 -1
  12. package/dist/capture/native-raw-dng.d.ts +6 -0
  13. package/dist/capture/native-raw-dng.js +16 -2
  14. package/dist/capture/native-raw-dng.js.map +1 -1
  15. package/dist/capture/sensor-raw-producer.js +3 -5
  16. package/dist/capture/sensor-raw-producer.js.map +1 -1
  17. package/dist/core/shared-helpers.d.ts +2 -0
  18. package/dist/core/shared-helpers.js +4 -0
  19. package/dist/core/shared-helpers.js.map +1 -1
  20. package/dist/core/version.d.ts +1 -1
  21. package/dist/core/version.js +1 -1
  22. package/dist/core/version.js.map +1 -1
  23. package/dist/equipment/generic-lens-catalogue.d.ts +8 -2
  24. package/dist/equipment/generic-lens-catalogue.js +31 -2
  25. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  26. package/dist/equipment/generic-tier-assets.js +2 -3
  27. package/dist/equipment/generic-tier-assets.js.map +1 -1
  28. package/dist/equipment/generic-tier-presets.js +2 -3
  29. package/dist/equipment/generic-tier-presets.js.map +1 -1
  30. package/dist/facade/display-view-gpu.d.ts +7 -4
  31. package/dist/facade/display-view-gpu.js +4 -4
  32. package/dist/facade/display-view-gpu.js.map +1 -1
  33. package/dist/facade/display-view-lens-signal.d.ts +1 -1
  34. package/dist/facade/display-view-lens-signal.js +5 -3
  35. package/dist/facade/display-view-lens-signal.js.map +1 -1
  36. package/dist/facade/display-view.d.ts +3 -1
  37. package/dist/facade/display-view.js +1 -1
  38. package/dist/facade/display-view.js.map +1 -1
  39. package/dist/facade/fast-methods.d.ts +50 -0
  40. package/dist/facade/fast-methods.js +50 -0
  41. package/dist/facade/fast-methods.js.map +1 -0
  42. package/dist/facade/fast-resolution.d.ts +12 -0
  43. package/dist/facade/fast-resolution.js +18 -0
  44. package/dist/facade/fast-resolution.js.map +1 -0
  45. package/dist/facade/generic-lens-capture.d.ts +3 -2
  46. package/dist/facade/generic-lens-capture.js +3 -2
  47. package/dist/facade/generic-lens-capture.js.map +1 -1
  48. package/dist/facade/simulate-capture.d.ts +51 -11
  49. package/dist/facade/simulate-capture.js +144 -75
  50. package/dist/facade/simulate-capture.js.map +1 -1
  51. package/dist/facade/simulate-display-view.d.ts +3 -2
  52. package/dist/facade/simulate-display-view.js +22 -12
  53. package/dist/facade/simulate-display-view.js.map +1 -1
  54. package/dist/index.d.ts +7 -0
  55. package/dist/index.js +5 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/optics/aperture-diameter.d.ts +47 -0
  58. package/dist/optics/aperture-diameter.js +50 -0
  59. package/dist/optics/aperture-diameter.js.map +1 -0
  60. package/dist/optics/detail-limit.d.ts +182 -0
  61. package/dist/optics/detail-limit.js +336 -0
  62. package/dist/optics/detail-limit.js.map +1 -0
  63. package/dist/optics/generic-lens-psf.d.ts +18 -2
  64. package/dist/optics/generic-lens-psf.js +25 -2
  65. package/dist/optics/generic-lens-psf.js.map +1 -1
  66. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  67. package/dist/optics/scene-to-sensor-irradiance.js +7 -0
  68. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  69. package/dist/output/crop-to-match-focal-length.d.ts +62 -0
  70. package/dist/output/crop-to-match-focal-length.js +63 -0
  71. package/dist/output/crop-to-match-focal-length.js.map +1 -0
  72. package/dist/output/developed-lens-corrections.js +2 -2
  73. package/dist/output/developed-lens-corrections.js.map +1 -1
  74. package/dist/output/generic-lens-corrections.d.ts +3 -1
  75. package/dist/output/generic-lens-corrections.js +8 -4
  76. package/dist/output/generic-lens-corrections.js.map +1 -1
  77. package/dist/output/lens-corrections.d.ts +12 -2
  78. package/dist/output/lens-corrections.js +22 -7
  79. package/dist/output/lens-corrections.js.map +1 -1
  80. package/dist/output/print-contrast-reference.js +1 -1
  81. package/dist/output/print-contrast-reference.js.map +1 -1
  82. package/dist/output/print-size-blur-noise.d.ts +110 -0
  83. package/dist/output/print-size-blur-noise.js +138 -0
  84. package/dist/output/print-size-blur-noise.js.map +1 -0
  85. package/dist/sensor/eqe-electron-rate.js +1 -7
  86. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  87. package/dist/sensor/exposure-window.d.ts +2 -0
  88. package/dist/sensor/exposure-window.js +2 -2
  89. package/dist/sensor/exposure-window.js.map +1 -1
  90. package/dist/sensor/readout-timing.js +2 -23
  91. package/dist/sensor/readout-timing.js.map +1 -1
  92. package/dist/sensor/response-application-compatibility.js +1 -6
  93. package/dist/sensor/response-application-compatibility.js.map +1 -1
  94. package/dist/sensor/responsivity-photocurrent.js +1 -7
  95. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  96. package/dist/sensor/sensor-characteristics.d.ts +137 -0
  97. package/dist/sensor/sensor-characteristics.js +115 -0
  98. package/dist/sensor/sensor-characteristics.js.map +1 -0
  99. package/dist/sensor/spatial-sample-reduction.js +1 -5
  100. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  101. package/dist/sensor/spectral-quadrature.js +1 -6
  102. package/dist/sensor/spectral-quadrature.js.map +1 -1
  103. package/dist/sensor/temporal-photo-signal.d.ts +3 -0
  104. package/dist/sensor/temporal-photo-signal.js +5 -2
  105. package/dist/sensor/temporal-photo-signal.js.map +1 -1
  106. package/dist/stabilization/system.d.ts +9 -0
  107. package/dist/stabilization/system.js +5 -6
  108. package/dist/stabilization/system.js.map +1 -1
  109. package/docs/AGENT_GUIDE.md +4 -0
  110. package/docs/API_REFERENCE.md +76 -2
  111. package/docs/QUICK_START.md +88 -6
  112. package/docs/USAGE.md +65 -2
  113. package/docs/api/capture-native-raw-dng.md +6 -0
  114. package/docs/api/core-version.md +1 -1
  115. package/docs/api/equipment-generic-lens-catalogue.md +7 -2
  116. package/docs/api/exports.json +152 -2
  117. package/docs/api/facade-display-view-gpu.md +5 -2
  118. package/docs/api/facade-fast-methods.md +91 -0
  119. package/docs/api/facade-simulate-capture.md +36 -11
  120. package/docs/api/facade-simulate-display-view.md +3 -2
  121. package/docs/api/optics-aperture-diameter.md +51 -0
  122. package/docs/api/optics-detail-limit.md +203 -0
  123. package/docs/api/output-crop-to-match-focal-length.md +67 -0
  124. package/docs/api/output-generic-lens-corrections.md +3 -1
  125. package/docs/api/output-lens-corrections.md +11 -1
  126. package/docs/api/output-print-size-blur-noise.md +115 -0
  127. package/docs/api/sensor-sensor-characteristics.md +105 -0
  128. package/llms.txt +4 -0
  129. package/package.json +1 -1
@@ -0,0 +1,91 @@
1
+ # facade/fast-methods.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
+ ## SIMULATE_CAPTURE_FAST_METHODS
6
+
7
+ 2.20.0: every speed-versus-accuracy choice of the capture facade and the display view (see the module comment).
8
+
9
+ ```ts
10
+ SIMULATE_CAPTURE_FAST_METHODS: readonly SimulateCaptureFastMethod[] = Object.freeze([
11
+ { id: "lens.catalogue/field-grid", override: "lens.catalogue", methods: "fast-and-exact", runs: ["simulateCapture"],
12
+ fast: "#568: the PSF blended over a field grid and tabulated expected electrons, verified per CFA phase against the exact chain",
13
+ exact: "the environment PSF quadrature with the PSF resolved at each query point", followsFormatDefault: true,
14
+ resolver: { file: CAPTURE, call: "resolveFast(catalogueFast, fastTop)" }, report: { applied: "lens.catalogue", assumption: "fast.lens.catalogue" } },
15
+ { id: "lens.catalogue/cats-eye", override: "lens.catalogue", methods: "fast-and-exact", runs: ["simulateCapture", "simulateDisplayView", "prepareDisplayView"],
16
+ fast: "#567: with lens.defocus, the lens's bokeh without its cat's eye (the axis kernel everywhere)",
17
+ exact: "the cat's eye shaped at each field point (fast wides and normals)", followsFormatDefault: true,
18
+ resolver: { file: CAPTURE, call: "resolveFast(catalogueFast, fastTop)" }, report: { applied: "lens.catalogue", assumption: "fast.lens.catalogue.catsEye" } },
19
+ { id: "lens.catalogue/view-psf-zones", override: "lens.catalogue", methods: "fast-and-exact", runs: ["simulateDisplayView", "prepareDisplayView"],
20
+ fast: "#569 (H1): 64 field zones along the longer side, each with the PSF at its centre",
21
+ exact: "one zone per site: the PSF at each site centre", followsFormatDefault: true,
22
+ resolver: { file: CAPTURE, call: "resolveFast(catalogueFast, fastTop)" }, report: { applied: "lens.catalogue", assumption: "view.lensPsf" } },
23
+ { id: "lens.diffraction/rolling-shutter-taps", override: "lens.diffraction", methods: "fast-and-exact", runs: ["simulateCapture"],
24
+ fast: "#565: under rolling-shutter rotation, each PSF tap reads the scene at its own source point",
25
+ exact: "each tap reads the scene at its site-lattice point (ties on chart boundaries resolve as on the lattice)", followsFormatDefault: true,
26
+ resolver: { file: CAPTURE, call: "resolveFast(diffractionFast, fastTop)" }, report: { applied: "lens.diffraction", assumption: "fast.lens.diffraction" } },
27
+ { id: "output.demosaic.ahd/view-low-signal-correction", override: "output.demosaic", methods: "fast-and-exact", runs: ["simulateDisplayView", "prepareDisplayView"],
28
+ fast: "(H3) 2048 draws per correction state, applied where a pixel's linear noise exceeds 0.01",
29
+ exact: "8192 draws per state, at the same 0.01 threshold", followsFormatDefault: true,
30
+ resolver: { file: CAPTURE, call: "resolveFast(demosaicFast, fastTop)" }, report: { applied: "output.demosaic.ahd", assumption: "view.demosaic" } },
31
+ { id: "view.uniform-pixel-sampling", override: "fast", methods: "fast-and-exact", runs: ["simulateDisplayView", "prepareDisplayView"],
32
+ fast: "(H2) a quarter of the samples per axis in display pixels whose pilot finds no detail",
33
+ exact: "the full samples per axis in every display pixel", followsFormatDefault: true,
34
+ resolver: { file: CAPTURE, call: "uniformSampling: fastTop" }, report: { assumption: "view.estimation" } },
35
+ { id: "prepareDisplayView.gpu", override: "fast", methods: "fast-and-exact", runs: ["prepareDisplayView"],
36
+ fast: "gpu \"auto\": WebGPU float32 where a conforming device supports the request (a bounded approximation), else the CPU reference",
37
+ exact: "gpu \"off\": the CPU reference; an explicit gpu wins", followsFormatDefault: true,
38
+ resolver: { file: VIEW, call: "parsePrepareDisplayViewOptions(prepareOptions, (options as { fast?: unknown } | null | undefined)?.fast !== false)" },
39
+ report: { field: "diagnostics.fast.execution" } },
40
+ { id: "sensor.midGrey", override: "fast", methods: "fast-and-exact", runs: ["simulateCapture", "simulateDisplayView", "prepareDisplayView"],
41
+ fast: "#535: the mid-gray noise figure is not computed (about 0.3 ms)", exact: "result.sensor.midGrey is computed", followsFormatDefault: true,
42
+ resolver: { file: CAPTURE, call: "fastTop ? undefined : 0" }, report: { field: "sensor.midGrey" } },
43
+ { id: "lens.diffraction", override: "lens.diffraction", methods: "exact-only", runs: ["simulateCapture", "simulateDisplayView", "prepareDisplayView"],
44
+ fast: "", exact: "the diffraction kernels (a fast method only under rolling-shutter rotation, above)", followsFormatDefault: true,
45
+ resolver: { file: CAPTURE, call: "resolveFast(diffractionFast, fastTop)" }, report: { applied: "lens.diffraction" } },
46
+ { id: "sensor.antiAliasing", override: "sensor.antiAliasing", methods: "exact-only", runs: ["simulateCapture", "simulateDisplayView", "prepareDisplayView"],
47
+ fast: "", exact: "the point-splitting filter", followsFormatDefault: true,
48
+ resolver: { file: CAPTURE, call: "fastInUse" }, report: { applied: "sensor.antiAliasing" } },
49
+ { id: "sensor.pixelAperture.box", override: "sensor.pixelAperture", methods: "exact-only", runs: ["simulateCapture", "simulateDisplayView", "prepareDisplayView"],
50
+ fast: "", exact: "the N × N midpoint rule", followsFormatDefault: true,
51
+ resolver: { file: CAPTURE, call: "fastInUse" }, report: { applied: "sensor.pixelAperture.box" } },
52
+ { id: "output.demosaic.ahd", override: "output.demosaic", methods: "exact-only", runs: ["simulateCapture"],
53
+ fast: "", exact: "the AHD demosaic of the capture (a fast method only in the display view's correction, above)", followsFormatDefault: true,
54
+ resolver: { file: CAPTURE, call: "resolveFast(demosaicFast, fastTop)" }, report: { applied: "output.demosaic.ahd" } }
55
+ ] as const satisfies readonly SimulateCaptureFastMethod[])
56
+ ```
57
+
58
+ ## SimulateCaptureFastMethod
59
+
60
+ One speed-versus-accuracy choice of {@link SIMULATE_CAPTURE_FAST_METHODS} (2.20.0).
61
+
62
+ ```ts
63
+ export interface SimulateCaptureFastMethod {
64
+ /** A short id: the option path and, where one option has several fast methods, the method. */
65
+ id: string;
66
+ /**
67
+ * The request path whose record takes the `fast` override (`allowKeys` path in simulate-capture.ts), or "fast" when only the
68
+ * top-level flag sets it (a choice with no option record of its own).
69
+ */
70
+ override: string;
71
+ /** "fast-and-exact": both methods exist; "exact-only": the record takes `fast`, but there is no fast method yet (reports false). */
72
+ methods: "fast-and-exact" | "exact-only";
73
+ /** Where it applies. */
74
+ runs: readonly ("simulateCapture" | "simulateDisplayView" | "prepareDisplayView")[];
75
+ /** The fast method and the exact one, in words (empty for exact-only). */
76
+ fast: string;
77
+ exact: string;
78
+ /** Always true: no choice keeps its own default (the test checks it). */
79
+ followsFormatDefault: true;
80
+ /**
81
+ * The resolver call in the source (`resolveFast(<record flag>, fastTop)`, or `fastTop` itself for a top-level choice), which the
82
+ * test finds in the named file.
83
+ */
84
+ resolver: { file: string; call: string };
85
+ /**
86
+ * How the result reports it: the `diagnostics.fast.applied` key (true where the fast method ran), an assumption id the fast
87
+ * method adds, or another result field. At least one; the test finds each assumption id in the source.
88
+ */
89
+ report: { applied?: string; assumption?: string; field?: string };
90
+ }
91
+ ```
@@ -66,6 +66,8 @@ export interface DisplayViewResult {
66
66
  /** #516: as in the capture result. */
67
67
  isoControl: "manual" | "automatic"; autoIso?: SimulateCaptureAutoIsoReport };
68
68
  scene: SimulateCaptureResult["scene"];
69
+ /** #555, #535: as in the capture result. */
70
+ sensor: SimulateCaptureSensorReport;
69
71
  /**
70
72
  * Work done: real pixels sampled, real-site expectations evaluated, scene queries, display pixels sampled in full (detail) or with
71
73
  * a quarter of the samples per axis (uniform), display pixels whose value includes the AHD low-signal correction, and wall-clock
@@ -425,6 +427,12 @@ One part of a whole-frame DNG (#391): a region render's packed RAW and where its
425
427
  export interface SimulateCaptureDngPart {
426
428
  raw: Pick<NativeRawOutput, "codes" | "blackLevels" | "digitalSaturationCodes" | "saturationFlags" | "effectiveSaturationCodes">;
427
429
  region: { nativeRect: { x: number; y: number; width: number; height: number } };
430
+ /**
431
+ * 2.20.0: true when a fast method ran for this part (`Object.values(result.diagnostics.fast.applied).includes(true)`). The join
432
+ * refuses such a part unless its own request resolves to fast too (a DNG, JPEG or PNG is exact by default): render the parts with
433
+ * the same `fast` as the join, or with `fast: false`.
434
+ */
435
+ fast?: boolean;
428
436
  }
429
437
  ```
430
438
 
@@ -450,8 +458,10 @@ export type SimulateCaptureFastOption = typeof SIMULATE_CAPTURE_FAST_OPTIONS[num
450
458
 
451
459
  ```ts
452
460
  export interface SimulateCaptureFastReport {
453
- /** True when `fast: true` was given at the top level or in any option's record. */
461
+ /** 2.20.0: the resolved top-level value (`fast`, else the format default), or true when any option's record sets `fast: true`. */
454
462
  requested: boolean;
463
+ /** 2.20.0: where the top-level value came from: the request's `fast`, or the format default (see {@link SimulateCaptureOptions.fast}). */
464
+ defaultedFrom: "request" | "format";
455
465
  /** Each fast-capable option that ran, in {@link SIMULATE_CAPTURE_FAST_OPTIONS} order: true where its fast method ran. */
456
466
  applied: Readonly<Partial<Record<SimulateCaptureFastOption, boolean>>>;
457
467
  /** `engine-reference` when every option ran exact; `approximation-with-a-priori-bound` when any fast method ran (the WebGPU execution record's values). */
@@ -462,6 +472,9 @@ export interface SimulateCaptureFastReport {
462
472
  ## SimulateCaptureLensCatalogueRecord
463
473
 
464
474
  `lens.catalogue` as a record (#568): the lens id as `kind` and the `fast` override (see {@link SimulateCaptureOptions.fast}).
475
+ Fast: the tabulated fast method (#568), and with `lens.defocus` the lens's bokeh without its cat's eye (#567; fast wides and
476
+ normals only), whose field-varying PSF needs a finer fast grid (measured 2.2–3.4× slower). Exact: the per-site PSF with the
477
+ cat's eye. Blades and spherical aberration always apply (they cost nothing).
465
478
 
466
479
  ```ts
467
480
  export interface SimulateCaptureLensCatalogueRecord { kind: string; fast?: boolean }
@@ -557,9 +570,9 @@ export interface SimulateCaptureOptions {
557
570
  * factor and it replaces the tier lens's focal length and f-number range (the transmission is kept). The capture renders the
558
571
  * lens's character, all labelled approximations: its PSF (diffraction with the lens's Seidel aberrations, axial and lateral
559
572
  * colour, at the resolved f-number and focus) through the environment PSF quadrature, its radial distortion, its vignetting
560
- * and, for the fisheyes, the equisolid projection. Flare, focus breathing and bokeh shape are not rendered. Not combinable
573
+ * and, for the fisheyes, the equisolid projection. Flare and focus breathing are not rendered. Not combinable
561
574
  * with `prime`, `focalLengthMm`, `projection`, `distortion`, `vignetting`, `psf`, `diffraction` or `pupilSampling` (the
562
- * lens sets them); `defocus` convolves the disc into the lens's PSF. The display view renders it with the PSF taken per field zone (#569; docs/DISPLAY_VIEW.md). Default off.
575
+ * lens sets them); `defocus` convolves the lens's bokeh kernel (#567) into its PSF. The display view renders it with the PSF taken per field zone (#569; docs/DISPLAY_VIEW.md). Default off.
563
576
  * #568: the record form `{ kind: <lens id>, fast }` overrides the top-level `fast` flag for this option; the fast method
564
577
  * blends the PSF between exactly resolved kernels on a field grid and tabulates each site's expected signal
565
578
  * (docs/LENS_CATALOGUE.md, "Fast method").
@@ -700,7 +713,7 @@ export interface SimulateCaptureOptions {
700
713
  exposureCompensationStops?: number;
701
714
  /** Default "scene-illuminant". */
702
715
  whiteBalance?: SimulateCaptureWhiteBalance;
703
- /** JPEG constant quantization step 1-255 (not a manufacturer 'quality'). Default 2. */
716
+ /** JPEG constant quantization step 1-255 (not a manufacturer 'quality'). Default 1 (2.20.0; 2 before), the full-quality export. */
704
717
  jpegQuantizationStep?: number;
705
718
  /** PNG only. Default "srgb". */
706
719
  colorEncoding?: SimulateCaptureColorEncoding;
@@ -715,8 +728,13 @@ export interface SimulateCaptureOptions {
715
728
  demosaic?: SimulateCaptureDemosaic | SimulateCaptureDemosaicRecord;
716
729
  /** Default "margin". */
717
730
  borderHandling?: SimulateCaptureBorderHandling;
718
- /** DNG only: optional capture-event time (YYYY-MM-DDTHH:mm:ss.sssZ); without it no date tags are written. */
719
- dng?: { capturedAtUtc?: string };
731
+ /**
732
+ * DNG only: optional capture-event time (YYYY-MM-DDTHH:mm:ss.sssZ); without it no date tags are written. Opt-in (#587), with
733
+ * `lens.catalogue`: `lensCorrectionOpcodes` writes the selected corrections of {@link SimulateCaptureLensCorrections} into the DNG as
734
+ * optional DNG opcodes in OpcodeList3 (FixVignetteRadial, WarpRectilinear with the zoom-to-fill; docs/NATIVE_RAW_AND_PRINT.md), for RAW editors
735
+ * that apply them; the RAW codes are unchanged. Whole frame only (no `region`). All off (or omitted): the same DNG bytes as without it.
736
+ */
737
+ dng?: { capturedAtUtc?: string; lensCorrectionOpcodes?: SimulateCaptureLensCorrections };
720
738
  /**
721
739
  * Opt-in (2.4) text written into the file for formats "jpeg", "png" and "dng": the caller's description,
722
740
  * software, copyright and comment, unchanged (JPEG XMP + COM, PNG tEXt/iTXt, DNG TIFF tags + XMP; see the
@@ -768,7 +786,8 @@ export interface SimulateCaptureOptions {
768
786
  };
769
787
  /** Camera motion and shutter type. Default: still camera, global electronic shutter. */
770
788
  motion?: SimulateCaptureMotion;
771
- sensor?: {
789
+ /** #555: plus the sensor characteristics ({@link SimulateCaptureSensorCharacteristics}), each off by default. */
790
+ sensor?: SimulateCaptureSensorCharacteristics & {
772
791
  /**
773
792
  * Pixel pitch in micrometres, 0.5–1000. Default 6. The simulated sensor is output width × pitch wide. 2.5: above 100 µm the
774
793
  * capture is a downsized view of a whole frame (for example 36 mm / 48 pixels = 750 µm), recorded in the assumption
@@ -795,10 +814,14 @@ export interface SimulateCaptureOptions {
795
814
  pixelAperture?: SimulateCapturePixelAperture;
796
815
  };
797
816
  /**
798
- * Exact or fast (#395). `false` or omitted: every option runs its exact method, the engine reference. `true`: each option
799
- * listed in {@link SIMULATE_CAPTURE_FAST_OPTIONS} runs its fast method where it has one, a measured, bounded approximation of
800
- * the exact one; an option without a fast method runs exact. An option's own record `fast` overrides this for that option
801
- * only. `diagnostics.fast` reports what was requested and what ran. `lens.catalogue` has a fast method (#568); the other options run exact.
817
+ * Exact or fast (#395). `false`: every option runs its exact method, the engine reference. `true`: each option listed in
818
+ * {@link SIMULATE_CAPTURE_FAST_OPTIONS} runs its fast method where it has one, a measured, bounded approximation of the exact
819
+ * one; an option without a fast method runs exact. An option's own record `fast` overrides this for that option only.
820
+ * Omitted (2.20.0), the format decides: fast for `output.format` "srgb-8" and "srgb-16" and for the display view, exact for
821
+ * "dng", "jpeg" and "png" (exports) and for `simulateIdealReference`. `diagnostics.fast` reports the resolved value, where it
822
+ * came from and what ran. Fast methods: `lens.catalogue` (#568, #567) and `lens.diffraction` under rolling-shutter rotation
823
+ * (#565); in the display view also its catalogue PSF zones, its sampling of uniform pixels and its AHD low-signal correction
824
+ * (docs/DISPLAY_VIEW.md). The exact result also carries `result.sensor.midGrey` (#535). Calculators should pass `fast: false`.
802
825
  */
803
826
  fast?: boolean;
804
827
  /** Aborting cancels the running task; the promise rejects with a domain error and returns no partial image. */
@@ -994,6 +1017,8 @@ export interface SimulateCaptureResult {
994
1017
  limitations: readonly string[];
995
1018
  };
996
1019
  assumptions: readonly SimulateCaptureAssumption[];
1020
+ /** #555, #535: the sensor characteristics used, the calculated dynamic range and (when the request resolves to exact) the mid-grey noise figure. */
1021
+ sensor: SimulateCaptureSensorReport;
997
1022
  }
998
1023
  ```
999
1024
 
@@ -65,9 +65,10 @@ responses through the environment chain, the diffraction kernels, readout moment
65
65
  about 0.3–1.3 s per call on a 24 MP request; a region costs only its own pixels. Errors in the request are raised here;
66
66
  errors in the view by `render`.
67
67
 
68
- 2.11 (#481): `prepareOptions.gpu` chooses the renderer: `"off"` (default) is the CPU reference, today's bytes; `"auto"` renders
68
+ 2.11 (#481): `prepareOptions.gpu` chooses the renderer: `"off"` is the CPU reference, today's bytes; `"auto"` renders
69
69
  on WebGPU when a conforming device is available and the request is supported, else on the CPU, labelled; `"require"` fails
70
- instead of falling back (`ImageKernelExecutionError`, acquisition failures here, later ones from `render`). `gpuDevice` is a
70
+ instead of falling back (`ImageKernelExecutionError`, acquisition failures here, later ones from `render`). 2.20.0: the default is
71
+ `"auto"` when the request resolves to fast (the view's default) and `"off"` with `fast: false`. `gpuDevice` is a
71
72
  WebGPU entry point or an `ImageKernelAccelerator` whose device the view shares; without it the engine acquires one device
72
73
  for the prepared view (the request's `signal` aborts the acquisition) and `dispose()` releases it. The realm's
73
74
  `setWebGpuEnabled(false)` vetoes both GPU modes (`webgpu-disabled`). See docs/DISPLAY_VIEW.md, "Rendering on the GPU".
@@ -0,0 +1,51 @@
1
+ # optics/aperture-diameter.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
+ ## ApertureDiameter
6
+
7
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
8
+
9
+ ```ts
10
+ export interface ApertureDiameter {
11
+ /** Focal length supplied by the caller, in millimetres. */
12
+ focalLengthMm: number;
13
+ /** f-number supplied by the caller. */
14
+ fNumber: number;
15
+ /** Entrance-pupil diameter D = focalLengthMm / fNumber, in millimetres. */
16
+ entrancePupilDiameterMm: number;
17
+ }
18
+ ```
19
+
20
+ ## calculateApertureDiameter
21
+
22
+ Entrance-pupil (aperture) diameter in millimetres: focal length ÷ f-number.
23
+
24
+ Exact by the definition of the f-number for an ideal lens focused at infinity. It is the
25
+ entrance pupil (the aperture stop as seen from the front), not the diaphragm's physical
26
+ opening. Close focus (working f-number N·(1 + m/P) with pupil magnification P) and focal-length
27
+ changes while focusing are not modelled.
28
+
29
+ Example: 50 mm at f/2 gives 25 mm; at f/4, 12.5 mm (half the diameter, a quarter of the area:
30
+ two stops).
31
+
32
+ ```ts
33
+ export function calculateApertureDiameter(
34
+ input: CalculateApertureDiameterInput
35
+ ): CalculationResult<ApertureDiameter>;
36
+ ```
37
+
38
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
39
+
40
+ ## CalculateApertureDiameterInput
41
+
42
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
43
+
44
+ ```ts
45
+ export interface CalculateApertureDiameterInput {
46
+ /** Focal length of the lens in millimetres (the physical focal length, not a 35 mm equivalent). */
47
+ focalLengthMm: number;
48
+ /** f-number set on the lens (N = focal length / entrance-pupil diameter). */
49
+ fNumber: number;
50
+ }
51
+ ```
@@ -0,0 +1,203 @@
1
+ # optics/detail-limit.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
+ ## calculateDetailLimit
6
+
7
+ Which limits detail for given settings: the lens (diffraction at this f-number, aberrations, defocus) or the sensor's pixel
8
+ pitch. Returns the optics' resolution (MTF50 and the diffraction cut-off, cycles/mm), the sampling's (Nyquist frequency
9
+ and, with a Bayer filter, the red and blue sites'), the system MTF50 (optics × pixel aperture), the f-number from which
10
+ diffraction limits on this sensor, and which one limits.
11
+
12
+ The sensor limits when the optics still pass 50 % contrast at the Nyquist frequency 1/(2p); otherwise the lens does,
13
+ through diffraction when its wavefront error is within the Maréchal criterion (RMS ≤ λ/14), else through aberrations or
14
+ defocus. The optics MTF is the exact scalar OTF of the stated wavefront at one wavelength; the decision rule and the
15
+ catalogue lenses' wavefronts are approximations.
16
+
17
+ Example: an ideal 50 mm lens at f/22 on a 36 × 24 mm, 6000-pixel-wide sensor (6 µm) is limited by diffraction (optics
18
+ MTF50 ≈ 33 cycles/mm against a Nyquist frequency of ≈ 83); at f/4 the pixel pitch limits.
19
+
20
+ ```ts
21
+ export function calculateDetailLimit(input: CalculateDetailLimitInput): CalculationResult<DetailLimit>;
22
+ ```
23
+
24
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
25
+
26
+ ## CalculateDetailLimitInput
27
+
28
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
29
+
30
+ ```ts
31
+ export interface CalculateDetailLimitInput {
32
+ /** Active imaging area and horizontal pixel count; pitch = widthMm / pixelWidth. */
33
+ sensor: {
34
+ widthMm: number;
35
+ heightMm: number;
36
+ pixelWidth: number;
37
+ /** Default `"bayer"`. `"none"`: every site records full detail (a monochrome sensor). */
38
+ colorFilterArray?: "bayer" | "none";
39
+ };
40
+ lens: DetailLimitIdealLens | DetailLimitCatalogueLens;
41
+ /** f-number set on the lens. */
42
+ fNumber: number;
43
+ /**
44
+ * Focus and subject distances from the lens in meters. Omitted: focused at infinity, subject in focus. `subjectDistanceM`
45
+ * omitted: the subject is at the focus distance.
46
+ */
47
+ focus?: { focusDistanceM: number; subjectDistanceM?: number };
48
+ /** Field height as a fraction of the half-diagonal, 0 (center, default) to 1 (corner). Only aberrations depend on it. */
49
+ fieldPosition?: number;
50
+ /** Wavelength in nanometres, 380–780. Default 550. */
51
+ wavelengthNm?: number;
52
+ }
53
+ ```
54
+
55
+ ## DETAIL_LIMIT_LIMITS
56
+
57
+ Limits of `calculateDetailLimit`: the wavelength range and the largest wavefront (sum of the magnitudes of its terms, in
58
+ waves) whose transfer function is integrated. Beyond it the blur is geometric and the quadrature would grow without
59
+ bound; use `calculateDefocusCircle` for such a blur.
60
+
61
+ ```ts
62
+ DETAIL_LIMIT_LIMITS = Object.freeze({ minimumWavelengthNm: 380, maximumWavelengthNm: 780, maximumWavefrontWaves: 64 })
63
+ ```
64
+
65
+ ## DetailLimit
66
+
67
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
68
+
69
+ ```ts
70
+ export interface DetailLimit {
71
+ /** The lens or the sensor. */
72
+ limitedBy: DetailLimitedBy;
73
+ /** Diffraction, aberrations or defocus (the lens), or the pixel pitch (the sensor). */
74
+ limitingFactor: DetailLimitingFactor;
75
+ /** Optics MTF50 / Nyquist frequency: at least 1 when the sensor limits. */
76
+ opticsToNyquistRatio: number;
77
+ fNumber: number;
78
+ wavelengthNm: number;
79
+ /** Physical focal length used (the catalogue lens's on this sensor), mm. */
80
+ focalLengthMm: number;
81
+ fieldPosition: number;
82
+ optics: DetailLimitOptics;
83
+ sampling: DetailLimitSampling;
84
+ system: DetailLimitSystem;
85
+ /**
86
+ * The f-number at which an aberration-free lens in focus has its MTF50 at the Nyquist frequency (2·p·0.40397/λ at
87
+ * infinity focus): from it on, diffraction limits detail on this sensor whatever the lens.
88
+ */
89
+ diffractionLimitedFNumber: number;
90
+ basis: Readonly<Record<"optics" | "sampling" | "system" | "limitedBy", DetailLimitBasis>>;
91
+ }
92
+ ```
93
+
94
+ ## DetailLimitBasis
95
+
96
+ Whether a quantity is exact under its stated conditions or an approximation.
97
+
98
+ ```ts
99
+ export interface DetailLimitBasis { kind: "exact" | "approximation"; statement: string }
100
+ ```
101
+
102
+ ## DetailLimitCatalogueLens
103
+
104
+ A generic catalogue lens (`GENERIC_LENS_IDS`), resolved for the sensor as `resolveGenericLens` does.
105
+
106
+ ```ts
107
+ export interface DetailLimitCatalogueLens {
108
+ kind: "catalogue";
109
+ lensId: string;
110
+ }
111
+ ```
112
+
113
+ ## DetailLimitedBy
114
+
115
+ Which component limits detail.
116
+
117
+ ```ts
118
+ export type DetailLimitedBy = "lens" | "sensor";
119
+ ```
120
+
121
+ ## DetailLimitIdealLens
122
+
123
+ A lens with no aberrations (diffraction only).
124
+
125
+ ```ts
126
+ export interface DetailLimitIdealLens {
127
+ kind: "ideal";
128
+ /** Physical focal length in millimeters (for the working f-number and the defocus of a subject off the focus distance). */
129
+ focalLengthMm: number;
130
+ }
131
+ ```
132
+
133
+ ## DetailLimitingFactor
134
+
135
+ What limits it: the lens's diffraction, aberrations or defocus, or the sensor's pixel pitch.
136
+
137
+ ```ts
138
+ export type DetailLimitingFactor = "diffraction" | "aberrations" | "defocus" | "pixel-pitch";
139
+ ```
140
+
141
+ ## DetailLimitOptics
142
+
143
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
144
+
145
+ ```ts
146
+ export interface DetailLimitOptics {
147
+ /** Working f-number N_w = N·v/f (thin-lens image distance v; N at infinity focus). */
148
+ workingFNumber: number;
149
+ /** Diffraction cut-off 1/(λ·N_w) in cycles/mm: no optics passes detail beyond it. */
150
+ cutoffCyclesPerMm: number;
151
+ /** Frequency where the optics' MTF falls to 0.5, the lower of the two directions, cycles/mm. */
152
+ mtf50CyclesPerMm: number;
153
+ /** MTF50 for detail along the radius from the image center (tangential), cycles/mm. */
154
+ tangentialMtf50CyclesPerMm: number;
155
+ /** MTF50 for detail across the radius (sagittal), cycles/mm. Equal to the tangential value at the center. */
156
+ sagittalMtf50CyclesPerMm: number;
157
+ /** MTF50 of an aberration-free lens in focus at the same working f-number (0.40397·cut-off), cycles/mm. */
158
+ diffractionMtf50CyclesPerMm: number;
159
+ /** Optics MTF at the sensor's Nyquist frequency (lower direction). */
160
+ mtfAtNyquist: number;
161
+ /** RMS wavefront error in waves at the wavelength: of the aberrations, of the defocus, and of both together. */
162
+ rmsWavefrontErrorWaves: { aberrations: number; defocus: number; total: number };
163
+ /** Whether the total RMS wavefront error is within the Maréchal criterion λ/14. */
164
+ withinMarechalCriterion: boolean;
165
+ /** Geometric blur-circle diameter of the defocus on the sensor, mm (0 in focus). */
166
+ defocusBlurDiameterMm: number;
167
+ }
168
+ ```
169
+
170
+ ## DetailLimitSampling
171
+
172
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
173
+
174
+ ```ts
175
+ export interface DetailLimitSampling {
176
+ colorFilterArray: "bayer" | "none";
177
+ /** Pixel pitch in micrometres: widthMm / pixelWidth. */
178
+ pixelPitchMicrometers: number;
179
+ /** 1/(2p), cycles/mm: the finest detail the pixel grid records without aliasing (Bayer green sites too, along rows and columns). */
180
+ nyquistCyclesPerMm: number;
181
+ /** Bayer only: 1/(4p), the Nyquist frequency of the red and blue sites. Null without a color filter array. */
182
+ redBlueNyquistCyclesPerMm: number | null;
183
+ /** Pixel aperture MTF at the Nyquist frequency: sinc(1/2) = 2/π. */
184
+ pixelApertureMtfAtNyquist: number;
185
+ /** Frequency where the pixel aperture MTF falls to 0.5 (0.60335/p), cycles/mm: beyond the Nyquist frequency. */
186
+ pixelApertureMtf50CyclesPerMm: number;
187
+ }
188
+ ```
189
+
190
+ ## DetailLimitSystem
191
+
192
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
193
+
194
+ ```ts
195
+ export interface DetailLimitSystem {
196
+ /** MTF50 of optics × pixel aperture (lower direction), cycles/mm. Detail above the Nyquist frequency aliases. */
197
+ mtf50CyclesPerMm: number;
198
+ /** The same in line pairs per picture height (cycles/mm × heightMm). */
199
+ mtf50LinePairsPerPictureHeight: number;
200
+ /** System MTF at the Nyquist frequency (lower direction). */
201
+ mtfAtNyquist: number;
202
+ }
203
+ ```
@@ -0,0 +1,67 @@
1
+ # output/crop-to-match-focal-length.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
+ ## calculateCropToMatchFocalLength
6
+
7
+ The centred crop that makes an image from a shorter lens match a longer lens's framing from the
8
+ same position: crop factor target ÷ focal length, and the pixels and megapixels left.
9
+
10
+ Exact for ideal rectilinear lenses focused at infinity, to whole pixels (the crop is
11
+ `calculateCenteredCrop`'s, which keeps the aspect ratio). At close focus the matching factor is
12
+ larger, F·(d − f)/(f·(d − F)) at object distance d (thin lens); that, distortion and focus
13
+ breathing are not modelled. The crop has the longer lens's framing and perspective but fewer
14
+ pixels: it records no more detail than the cropped part of the shorter lens's image.
15
+
16
+ Example: a 24 MP (6000 × 4000) image at 50 mm cropped to match 100 mm: factor 2, 3000 × 2000,
17
+ 6 MP.
18
+
19
+ ```ts
20
+ export function calculateCropToMatchFocalLength(
21
+ input: CalculateCropToMatchFocalLengthInput
22
+ ): CalculationResult<CropToMatchFocalLength>;
23
+ ```
24
+
25
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
26
+
27
+ ## CalculateCropToMatchFocalLengthInput
28
+
29
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
30
+
31
+ ```ts
32
+ export interface CalculateCropToMatchFocalLengthInput {
33
+ /** Focal length the image was taken with, in millimetres. */
34
+ focalLengthMm: number;
35
+ /** Longer focal length whose framing the crop should match, in millimetres. At least `focalLengthMm`. */
36
+ targetFocalLengthMm: number;
37
+ /** Image width in pixels (for example the sensor's output width). */
38
+ pixelWidth: number;
39
+ /** Image height in pixels. */
40
+ pixelHeight: number;
41
+ }
42
+ ```
43
+
44
+ ## CropToMatchFocalLength
45
+
46
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
47
+
48
+ ```ts
49
+ export interface CropToMatchFocalLength {
50
+ /** Focal length supplied by the caller, in millimetres. */
51
+ focalLengthMm: number;
52
+ /** Target focal length supplied by the caller, in millimetres. */
53
+ targetFocalLengthMm: number;
54
+ /** Linear crop factor targetFocalLengthMm / focalLengthMm (1 means no crop). */
55
+ cropFactor: number;
56
+ /** Width left after the centred crop, in pixels (as `calculateCenteredCrop`). */
57
+ pixelWidth: number;
58
+ /** Height left after the centred crop, in pixels. */
59
+ pixelHeight: number;
60
+ /** Pixels left, in millions. */
61
+ megapixels: number;
62
+ /** Megapixels of the uncropped image. */
63
+ sourceMegapixels: number;
64
+ /** Fraction of the original pixel area retained, about 1 / cropFactor². */
65
+ retainedAreaFraction: number;
66
+ }
67
+ ```
@@ -6,7 +6,9 @@ The package and root API versions are recorded once in [Navigation](../API_REFER
6
6
 
7
7
  Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
8
8
  r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the
9
- relative-illumination gain at the state's aperture (strength 1). The result passes `parseGenericLensCorrectionProfile`.
9
+ relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component
10
+ (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
11
+ sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
10
12
 
11
13
  ```ts
12
14
  export function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile;
@@ -31,7 +31,9 @@ This call is synchronous; any supplied provider must follow its explicit synchro
31
31
  ## calculatePeripheralIlluminationCorrection
32
32
 
33
33
  Applies a gain after capture and reports the variance before clipping. It does not rewrite
34
- the photon or SNR (signal-to-noise ratio) history of the capture.
34
+ the photon or SNR (signal-to-noise ratio) history of the capture. The gain is T^(−strength), T the relative illumination at
35
+ `imagePointMm`: E(ρ) of the component's profile, divided by det J(ρ_i) when the component has a `distortionAreaFactor` (#584;
36
+ then `imagePointMm` is the capture's sensor point, where the capture applied both).
35
37
 
36
38
  ```ts
37
39
  export function calculatePeripheralIlluminationCorrection(input: {
@@ -77,6 +79,14 @@ export interface IlluminationLensCorrection extends CorrectionComponentIdentity
77
79
  kind: "peripheral-illumination";
78
80
  profile: IlluminationVignettingProfile;
79
81
  strength: number;
82
+ /**
83
+ * Optional (#584): the capture's radial distortion r_d = r(1 + k1ρ² + k2ρ⁴ + k3ρ⁶), whose area factor 1/det J the capture also
84
+ * multiplied into the irradiance (det J = (1 + k1ρ_i² + k2ρ_i⁴ + k3ρ_i⁶)(1 + 3k1ρ_i² + 5k2ρ_i⁴ + 7k3ρ_i⁶) at the ideal point ρ_i that
85
+ * the lens images to the sensor point). With it, the relative illumination corrected is E(ρ_s)/det J(ρ_i), evaluated at the
86
+ * capture's sensor (source) point, so strength 1 flattens a flat field. It holds whether or not the distortion itself is corrected.
87
+ * Without it the gain is 1/E alone, as before.
88
+ */
89
+ distortionAreaFactor?: RadialDistortionProfile;
80
90
  }
81
91
  ```
82
92