@photivra/engine 2.19.0 → 2.21.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 +289 -1
- package/CITATION.cff +1 -1
- package/NODE_SUPPORT.md +7 -5
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +21 -10
- package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
- package/dist/capture/dng-lens-correction-opcodes.js +119 -0
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
- package/dist/capture/exposure-build-up-stage.d.ts +16 -0
- package/dist/capture/exposure-build-up-stage.js +4 -0
- package/dist/capture/exposure-build-up-stage.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +2 -1
- package/dist/capture/native-environment-lean.js +42 -78
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-tabulated.js +4 -3
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/native-raw-dng.d.ts +6 -0
- package/dist/capture/native-raw-dng.js +16 -2
- package/dist/capture/native-raw-dng.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -2
- package/dist/capture/native-raw.js +5 -4
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +4 -1
- package/dist/capture/sensor-raw-producer.js +15 -8
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/core/shared-helpers.d.ts +2 -0
- package/dist/core/shared-helpers.js +4 -0
- package/dist/core/shared-helpers.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 +11 -4
- package/dist/equipment/generic-lens-catalogue.js +49 -7
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +32 -0
- package/dist/equipment/generic-tier-assets.js +18 -14
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.js +2 -3
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/equipment/sensor-profile-body.d.ts +78 -0
- package/dist/equipment/sensor-profile-body.js +77 -0
- package/dist/equipment/sensor-profile-body.js.map +1 -0
- package/dist/equipment/sensor-profiles.d.ts +8 -0
- package/dist/equipment/sensor-profiles.js +82 -0
- package/dist/equipment/sensor-profiles.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +2 -2
- package/dist/facade/catalogue-lens-signal.js +58 -71
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +2 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/defocus-image-space-provider.d.ts +7 -0
- package/dist/facade/defocus-image-space-provider.js +64 -76
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +2 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +13 -5
- package/dist/facade/display-view-gpu.js +4 -4
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +1 -1
- package/dist/facade/display-view-lens-signal.js +30 -26
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view.d.ts +3 -1
- package/dist/facade/display-view.js +6 -24
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +3 -1
- package/dist/facade/expected-signal-setup.js +1 -1
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +47 -14
- package/dist/facade/expected-signal.js +117 -112
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/exposure-build-up.d.ts +70 -0
- package/dist/facade/exposure-build-up.js +164 -0
- package/dist/facade/exposure-build-up.js.map +1 -0
- package/dist/facade/fast-methods.d.ts +50 -0
- package/dist/facade/fast-methods.js +58 -0
- package/dist/facade/fast-methods.js.map +1 -0
- package/dist/facade/fast-resolution.d.ts +12 -0
- package/dist/facade/fast-resolution.js +18 -0
- package/dist/facade/fast-resolution.js.map +1 -0
- package/dist/facade/generic-lens-capture.d.ts +5 -4
- package/dist/facade/generic-lens-capture.js +5 -4
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane-enable.d.ts +7 -0
- package/dist/facade/image-plane-enable.js +17 -0
- package/dist/facade/image-plane-enable.js.map +1 -0
- package/dist/facade/image-plane-lattice-data.d.ts +6 -0
- package/dist/facade/image-plane-lattice-data.js +10 -0
- package/dist/facade/image-plane-lattice-data.js.map +1 -0
- package/dist/facade/image-plane-support.d.ts +33 -0
- package/dist/facade/image-plane-support.js +11 -0
- package/dist/facade/image-plane-support.js.map +1 -0
- package/dist/facade/image-plane.d.ts +36 -0
- package/dist/facade/image-plane.js +353 -0
- package/dist/facade/image-plane.js.map +1 -0
- package/dist/facade/mixed-illumination.d.ts +108 -0
- package/dist/facade/mixed-illumination.js +174 -0
- package/dist/facade/mixed-illumination.js.map +1 -0
- package/dist/facade/representative-light-sources.d.ts +72 -0
- package/dist/facade/representative-light-sources.js +124 -0
- package/dist/facade/representative-light-sources.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +129 -4
- package/dist/facade/scene-radiance.js +48 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +88 -19
- package/dist/facade/simulate-capture.js +374 -145
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +3 -2
- package/dist/facade/simulate-display-view.js +36 -18
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.d.ts +13 -1
- package/dist/gpu/chart-composite-gpu.js +48 -9
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +4 -30
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
- package/dist/gpu/display-view-composite-material-module.js +39 -13
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-image-material-module.d.ts +86 -0
- package/dist/gpu/display-view-image-material-module.js +339 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +45 -0
- package/dist/gpu/display-view-image-quality.js +380 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
- package/dist/gpu/display-view-quality-gpu.js +136 -205
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
- package/dist/gpu/display-view-speed-gpu.js +131 -93
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
- package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
- package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
- package/dist/gpu/wgsl-exact-sum.js +42 -0
- package/dist/gpu/wgsl-exact-sum.js.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/motion/subject-motion.d.ts +66 -0
- package/dist/motion/subject-motion.js +52 -0
- package/dist/motion/subject-motion.js.map +1 -0
- package/dist/optics/aperture-diameter.d.ts +47 -0
- package/dist/optics/aperture-diameter.js +50 -0
- package/dist/optics/aperture-diameter.js.map +1 -0
- package/dist/optics/detail-limit.d.ts +182 -0
- package/dist/optics/detail-limit.js +336 -0
- package/dist/optics/detail-limit.js.map +1 -0
- package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
- package/dist/optics/generic-lens-lateral-color.js +77 -0
- package/dist/optics/generic-lens-lateral-color.js.map +1 -0
- package/dist/optics/generic-lens-psf.d.ts +18 -2
- package/dist/optics/generic-lens-psf.js +79 -48
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +7 -0
- package/dist/optics/lens-psf-profile.js +2 -1
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
- package/dist/optics/scene-to-sensor-irradiance.js +7 -0
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/output/crop-to-match-focal-length.d.ts +62 -0
- package/dist/output/crop-to-match-focal-length.js +63 -0
- package/dist/output/crop-to-match-focal-length.js.map +1 -0
- package/dist/output/developed-lens-corrections.d.ts +19 -5
- package/dist/output/developed-lens-corrections.js +30 -11
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +41 -3
- package/dist/output/generic-lens-corrections.js +57 -9
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/lens-corrections.d.ts +12 -2
- package/dist/output/lens-corrections.js +22 -7
- package/dist/output/lens-corrections.js.map +1 -1
- package/dist/output/print-contrast-reference.js +1 -1
- package/dist/output/print-contrast-reference.js.map +1 -1
- package/dist/output/print-plan.d.ts +41 -5
- package/dist/output/print-plan.js +59 -14
- package/dist/output/print-plan.js.map +1 -1
- package/dist/output/print-size-blur-noise.d.ts +110 -0
- package/dist/output/print-size-blur-noise.js +138 -0
- package/dist/output/print-size-blur-noise.js.map +1 -0
- package/dist/sensor/binomial-sample.d.ts +3 -0
- package/dist/sensor/binomial-sample.js +86 -0
- package/dist/sensor/binomial-sample.js.map +1 -0
- package/dist/sensor/eqe-electron-rate.js +1 -7
- package/dist/sensor/eqe-electron-rate.js.map +1 -1
- package/dist/sensor/exposure-window.d.ts +2 -0
- package/dist/sensor/exposure-window.js +2 -2
- package/dist/sensor/exposure-window.js.map +1 -1
- package/dist/sensor/readout-timing.js +2 -23
- package/dist/sensor/readout-timing.js.map +1 -1
- package/dist/sensor/response-application-compatibility.js +1 -6
- package/dist/sensor/response-application-compatibility.js.map +1 -1
- package/dist/sensor/responsivity-photocurrent.js +1 -7
- package/dist/sensor/responsivity-photocurrent.js.map +1 -1
- package/dist/sensor/sensor-characteristics.d.ts +147 -0
- package/dist/sensor/sensor-characteristics.js +126 -0
- package/dist/sensor/sensor-characteristics.js.map +1 -0
- package/dist/sensor/spatial-sample-reduction.js +1 -5
- package/dist/sensor/spatial-sample-reduction.js.map +1 -1
- package/dist/sensor/spectral-quadrature.js +1 -6
- package/dist/sensor/spectral-quadrature.js.map +1 -1
- package/dist/sensor/temporal-photo-signal.d.ts +3 -0
- package/dist/sensor/temporal-photo-signal.js +5 -2
- package/dist/sensor/temporal-photo-signal.js.map +1 -1
- package/dist/stabilization/system.d.ts +9 -0
- package/dist/stabilization/system.js +5 -6
- package/dist/stabilization/system.js.map +1 -1
- package/docs/AGENT_GUIDE.md +5 -0
- package/docs/API_REFERENCE.md +173 -2
- package/docs/QUICK_START.md +213 -6
- package/docs/USAGE.md +157 -3
- package/docs/api/capture-native-raw-dng.md +6 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +17 -7
- package/docs/api/equipment-sensor-profile-body.md +64 -0
- package/docs/api/equipment-sensor-profiles.md +86 -0
- package/docs/api/exports.json +357 -2
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +99 -0
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -0
- package/docs/api/facade-representative-light-sources.md +131 -0
- package/docs/api/facade-scene-radiance.md +54 -3
- package/docs/api/facade-simulate-capture.md +60 -15
- package/docs/api/facade-simulate-display-view.md +3 -2
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-aperture-diameter.md +51 -0
- package/docs/api/optics-detail-limit.md +203 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -0
- package/docs/api/output-crop-to-match-focal-length.md +67 -0
- package/docs/api/output-developed-lens-corrections.md +10 -1
- package/docs/api/output-generic-lens-corrections.md +40 -2
- package/docs/api/output-lens-corrections.md +11 -1
- package/docs/api/output-print-plan.md +35 -1
- package/docs/api/output-print-size-blur-noise.md +115 -0
- package/docs/api/sensor-sensor-characteristics.md +105 -0
- package/llms.txt +6 -0
- package/package.json +1 -1
package/THIRD_PARTY.md
CHANGED
|
@@ -31,21 +31,28 @@ This inventory does not replace a transitive dependency/SBOM review before a rel
|
|
|
31
31
|
|
|
32
32
|
## Pending licence and source reviews
|
|
33
33
|
|
|
34
|
-
These human reviews
|
|
34
|
+
These human reviews were **pending** until the owner signed all five off on 2026-11-09 (#544; see the status below). This inventory records them and the evidence for each. A review is complete only when the owner signs it off: a check by an AI system is not legal evidence (`CONTRIBUTING.md`). Each is described in full in its section below.
|
|
35
|
+
|
|
36
|
+
**Status on 2026-11-09.** The owner signed off all five reviews on [#544](https://github.com/Photivra/photivra/issues/544) on 2026-11-09: "castleCSF-derived contrast reference", "ReportLab/pypdf offline PDF test-artifact tools", "DNG/TIFF and JPEG sample provenance", "Colorimetry published constants", and "Transitive dependency / SBOM review" for 2.14.0–2.19.0 (from 2.20.0 that review is repeated for each release's exact lockfile, with the `npm run check:licenses` result and any new or changed licence listed in the release's pull request).
|
|
35
37
|
|
|
36
38
|
**Status on 2026-11-02.** 2.14.0 was tagged and published to npm with the owner's approval ([release record](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_2_14_0.md)) while these five reviews were open. **The owner accepted 2.14.0 shipping with these reviews open, 2026-11-02.** None of them is signed off. What each still needs is tracked in [#544](https://github.com/Photivra/photivra/issues/544). The evidence below was checked on 2026-11-02 against `main` at `v2.14.0` (`f4b09a7`), using the repository's own records.
|
|
37
39
|
|
|
38
40
|
| Review | Evidence checked on 2026-11-02 | Still needed from the owner |
|
|
39
41
|
| --- | --- | --- |
|
|
40
|
-
| castleCSF-derived contrast reference (`src/output/print-contrast-reference.ts`) | **Licence evidence complete.** The MIT text in `NOTICE` is identical to `docs/licenses/castleCSF-MIT.txt`. A licence-compliance pass on 2026-11-02 compared that file with upstream `LICENSE` at revision `f4b0b722af83001d7af979281e06ca642d36e4e8` through the GitHub API: identical. The adapter's header names the holder and is kept in the published `dist/output/print-contrast-reference.js`. |
|
|
41
|
-
| ReportLab/pypdf offline PDF test-artifact tools | The committed `output/pdf/print-proof-reference.pdf` has no `FontFile` entry and declares only `/BaseFont /Helvetica`, so no font program is embedded. The versions used are recorded as ReportLab 4.4.9 and pypdf 6.10.0 (`test/fixtures/print-detail/proof-layout-reference.json`, `proof-inspection-reference.json`, `docs/validation/print-196-candidate-evidence.json`). Neither tool is vendored or in the package.
|
|
42
|
-
| DNG/TIFF and JPEG sample provenance | All 26 tracked `.dng`/`.jpg` files name only "Photivra", "Photivra Virtual Camera" or "Photivra Print JPEG" (`strings` scan); no camera maker's name appears. The Adobe DNG notice is in `NOTICE`, `README.md` and the three DNG sources. **Not done:** byte-for-byte regeneration of the files. |
|
|
43
|
-
| Transitive dependency/SBOM review | **Evidence complete for the 2.14.0 lockfile.** Recount of `package-lock.json`: 168 entries, with the licence counts given in [its summary](#per-release-transitive-dependency--sbom-review); no entry is a runtime dependency. `npm run check:licenses` passes. GitHub reports MIT for all four GitHub Actions in the table above (their repositories' detected licence). |
|
|
44
|
-
| Colorimetry published constants (`src/color/`). See [Colorimetry published constants and excluded datasets](#colorimetry-published-constants-and-excluded-datasets). | No CIE table is committed: the largest set of decimal constants in `src/color/` is 37, in `color-matching-functions.ts` (the engine's own fit). Each constant's source is cited in that section.
|
|
42
|
+
| castleCSF-derived contrast reference (`src/output/print-contrast-reference.ts`) | **Licence evidence complete.** The MIT text in `NOTICE` is identical to `docs/licenses/castleCSF-MIT.txt`. A licence-compliance pass on 2026-11-02 compared that file with upstream `LICENSE` at revision `f4b0b722af83001d7af979281e06ca642d36e4e8` through the GitHub API: identical. The adapter's header names the holder and is kept in the published `dist/output/print-contrast-reference.js`. | **Signed off by the owner, 2026-11-09** (#544), after a scientific and source review: the adapter matches the pinned upstream code in every constant and step of its restricted domain (relative difference at most 3.8e-9, and 1.7e-15 against the 80-digit reference); where the paper's printed Eq. 21–22 differ from the code, the code wins ([the model/protocol](https://github.com/Photivra/photivra/blob/main/docs/PRINT_CONTRAST_REFERENCE.md)). |
|
|
43
|
+
| ReportLab/pypdf offline PDF test-artifact tools | The committed `output/pdf/print-proof-reference.pdf` has no `FontFile` entry and declares only `/BaseFont /Helvetica`, so no font program is embedded. The versions used are recorded as ReportLab 4.4.9 and pypdf 6.10.0 (`test/fixtures/print-detail/proof-layout-reference.json`, `proof-inspection-reference.json`, `docs/validation/print-196-candidate-evidence.json`). Neither tool is vendored or in the package. The licences of the exact versions were checked on 2026-11-09: ReportLab 4.4.9 is BSD ("BSD license", ReportLab Inc.; PyPI metadata; the `LICENSE` text was read from an unofficial mirror, unchanged since 2024-12-17) and pypdf 6.10.0 is BSD-3-Clause (PyPI metadata and the `LICENSE` at the pypdf `6.10.0` tag). The PDF embeds no library code or font program, and neither licence sets conditions on output made with the software, so no notice is owed for the generated artifact. Pillow (a ReportLab dependency, used to read the JPEG header; the bytes pass through unchanged) was also present when it was generated; its version was not recorded. | **Signed off by the owner, 2026-11-09** (#544). |
|
|
44
|
+
| DNG/TIFF and JPEG sample provenance | All 26 tracked `.dng`/`.jpg` files name only "Photivra", "Photivra Virtual Camera" or "Photivra Print JPEG" (`strings` scan); no camera maker's name appears. The Adobe DNG notice is in `NOTICE`, `README.md` and the three DNG sources. **Not done:** byte-for-byte regeneration of the files. | **Signed off by the owner, 2026-11-09** (#544). |
|
|
45
|
+
| Transitive dependency/SBOM review | **Evidence complete for the 2.14.0 lockfile.** Recount of `package-lock.json`: 168 entries, with the licence counts given in [its summary](#per-release-transitive-dependency--sbom-review); no entry is a runtime dependency. `npm run check:licenses` passes. GitHub reports MIT for all four GitHub Actions in the table above (their repositories' detected licence). | **Signed off by the owner for 2.14.0–2.19.0, 2026-11-09** (#544). From 2.20.0 it is reviewed per release: each release's pull request gives the `npm run check:licenses` result and any new or changed licence against the previous release's lockfile. |
|
|
46
|
+
| Colorimetry published constants (`src/color/`). See [Colorimetry published constants and excluded datasets](#colorimetry-published-constants-and-excluded-datasets). | No CIE table is committed: the largest set of decimal constants in `src/color/` is 37, in `color-matching-functions.ts` (the engine's own fit). Each constant's source is cited in that section. On 2026-11-09 every constant was re-read against its free source (paywalled primaries through free secondaries, and numerically against the free CIE datasets), with no mismatch. | **Signed off by the owner, 2026-11-09** (#544). |
|
|
47
|
+
| Sensor profile sources (#554, `src/equipment/sensor-profiles.ts`). See [Sensor profile sources](#sensor-profile-sources-554). | **Signed off by the owner on 2026-11-09** (sources approved as listed; astronomy-camera manuals accepted for sensor values only; the retailer copy for one profile accepted, labelled; links allowed in `docs/SENSOR_PROFILES.md` only). Ten free sources and three standards (ISO 12232 labelled paywalled); every value is labelled published, derived or design. | Nothing for #554. |
|
|
48
|
+
|
|
49
|
+
## Sensor profile sources (#554)
|
|
50
|
+
|
|
51
|
+
`src/equipment/sensor-profiles.ts` holds eight sensor profiles whose values are facts taken from manufacturer-published specifications (sensor dimensions, pixel counts, pixel pitch, full well, read noise, peak QE, ADC bit depth, frame rate) and one openly published paper (International Image Sensor Workshop 2017, paper R03, Table 1), or Photivra design values labelled as such. They are factual references (`reuseStatus: "factual-reference-only"`); no table, curve, image or text is copied, and no maker or product is named in the code, data or docs. Commercial or terms-restricted measurement databases and test websites were excluded and not used (owner decision, 2026-11-06). ISO 12232:2019 is named for a cross-check only; no value depends on it. Sources, with links, and derivations: `docs/SENSOR_PROFILES.md`. The owner signed off the source review on 2026-11-09 (table above).
|
|
45
52
|
|
|
46
53
|
## Licence summaries (prepared for owner sign-off)
|
|
47
54
|
|
|
48
|
-
Prepared on 2026-10-11 from local files only (repository files, `package-lock.json` and locally installed package metadata). These are factual summaries for the owner to sign off; they are **not** completed reviews, and
|
|
55
|
+
Prepared on 2026-10-11 from local files only (repository files, `package-lock.json` and locally installed package metadata). These are factual summaries for the owner to sign off; they are **not** completed reviews, and each item above stayed pending until the owner signed it off (2.14.0 was released with them open; all five were signed off on 2026-11-09). "Not verified" means the fact could not be checked locally.
|
|
49
56
|
|
|
50
57
|
### castleCSF-derived contrast reference
|
|
51
58
|
|
|
@@ -62,7 +69,7 @@ Prepared on 2026-10-11 from local files only (repository files, `package-lock.js
|
|
|
62
69
|
- **Permissions:** both licences permit commercial use, modification and redistribution in source and binary form.
|
|
63
70
|
- **Obligations:** keep the copyright notice, conditions and disclaimer on redistribution, and do not use the contributors' names for endorsement. Neither tool is vendored or redistributed here, so no notice is currently owed.
|
|
64
71
|
- **Concerns:**
|
|
65
|
-
- The
|
|
72
|
+
- The licences of the exact versions were checked on 2026-11-09: ReportLab 4.4.9 is BSD ("BSD license", ReportLab Inc.; PyPI metadata; the `LICENSE` text was read from an unofficial mirror, unchanged since 2024-12-17) and pypdf 6.10.0 is BSD-3-Clause (PyPI metadata and the `LICENSE` at the pypdf `6.10.0` tag). The PDF embeds no library code or font program, and neither licence sets conditions on output made with the software, so no notice is owed for the generated artifact. Pillow (a ReportLab dependency, used to read the JPEG header; the bytes pass through unchanged) was also present when it was generated; its version was not recorded.
|
|
66
73
|
- The ReportLab distribution also bundles font files, including DarkGarden (with a GPL copying file) and Vera. The committed `output/pdf/print-proof-reference.pdf` contains no `FontFile` entries and declares only `/BaseFont /Helvetica`, so no bundled font program is embedded (checked locally).
|
|
67
74
|
|
|
68
75
|
### DNG/TIFF and JPEG sample provenance
|
|
@@ -97,9 +104,13 @@ The colorimetry modules in `src/color/` (see [docs/COLORIMETRY.md](https://githu
|
|
|
97
104
|
| --- | --- | --- |
|
|
98
105
|
| 28 coefficients of the engine's own multi-lobe CIE 1931 CMF fit (`color-matching-functions.ts`) | Fitted by Photivra with `scripts/fit-cie-1931-cmf.mjs` to the CIE 1931 2° table, DOI 10.25039/CIE.DS.xvudnb9b (CIE, CC BY-SA 4.0). The model form, a sum of piecewise Gaussians, follows the idea in Wyman, Sloan & Shirley (2013), JCGT 2(2); none of that paper's coefficients or text are used. | Fit-time use only: the script downloads the table into a temporary directory outside the repository, checks its SHA-256 and deletes it. No CIE value is stored in the repository, the package, the tests or the script's output. The committed coefficients are our own least-squares result; 28 numbers cannot reproduce the 1,413-value table (maximum error 0.022). Attribution to the CIE dataset is given here and in `docs/COLORIMETRY.md`. Replaced the Wyman et al. coefficients by owner decision (2026-10-17). |
|
|
99
106
|
| Reference CIELAB and sRGB values of the engine's own color patches (`color-patches.ts`, `docs/COLOR_PATCHES.md`) | Computed by Photivra with `scripts/compute-color-patch-reference.mjs` from Photivra's own analytic spectra, using the CIE standard illuminant D65 table (DOI 10.25039/CIE.DS.hjfjmt59) and the CIE 1931 2° table (DOI 10.25039/CIE.DS.xvudnb9b), both CIE, CC BY-SA 4.0. The spectra themselves are Photivra's design; no ColorChecker, IT8 or other commercial target data, names, order or layout are used. | Computation-time use only, as for the CMF fit: the script downloads both tables into a temporary directory, checks their SHA-256 and deletes them. No CIE value is stored in the repository, the package, the tests or the script's output; the committed values (three CIELAB coordinates and one 8-bit sRGB colour per patch) are results computed from our spectra. Attribution to the CIE datasets is given here and in `docs/COLOR_PATCHES.md`. |
|
|
107
|
+
| Node spectra of the image-plane scene's print reflectance lattice (`image-plane-lattice-data.ts`, `docs/IMAGE_PLANE.md`, #581) | Fitted by Photivra with `scripts/fit-image-plane-lattice.mjs`: the LHTSS method of S. A. Burns, "Generating Reflectance Curves from sRGB Triplets", arXiv:1710.05732 v5 (2020), pp. 24–26 (the paper is CC BY-SA 4.0; its method is implemented from the equations, and neither its text nor its MATLAB code, also CC BY-SA 4.0, is used), against the CIE standard illuminant D65 table (DOI 10.25039/CIE.DS.hjfjmt59, https://files.cie.co.at/Publications-datasets/CIE_std_illum_D65.csv) and the CIE 1931 2° table (DOI 10.25039/CIE.DS.xvudnb9b, https://files.cie.co.at/Publications-datasets/CIE_xyz_1931_2deg.csv), both CIE, CC BY-SA 4.0. Owner decision 2026-11-09 (#581). | Fit-time use only, as for the CMF fit: the script downloads both tables into a temporary directory outside the repository, checks their SHA-256 and deletes them. No CIE value is stored in the repository, the package, the tests or the script's output. The committed data (120 spectra × 41 samples of 16 bits, delta-coded) are Photivra's own optimization results: each is the smoothest bounded spectrum that meets three integral constraints, and they cannot reproduce the tables. Attribution to the CIE datasets is given here and in `docs/IMAGE_PLANE.md`. |
|
|
108
|
+
| Representative light-source spectra (`representative-light-sources.ts`, `docs/LIGHT_SOURCES.md`, #557) | Photivra's own design (owner decision 2026-11-06): a constant plus Gaussian bands whose positions and widths Photivra chose for each kind of lamp, with two parameters per source solved by Photivra to put it on the Planckian locus at its stated CCT using the engine's own analytic CMF fit and Planck's law. No third-party spectral data is used: no CIE illuminant tables (the F-series fluorescent, LED-series or any other), no IES TM-30 or CIE test-sample reflectances, and no manufacturer or product spectra, names or data sheets. Their CCT and color-rendering figures are computed with the engine's own code and color patches only. | Photivra's own work under the project licence; nothing to attribute. Labeled "representative, not a named product". |
|
|
100
109
|
| Distinctness check of the color patches' published colors and names (`scripts/check-color-patch-distinctness.mjs`, `docs/COLOR_PATCHES.md`) | Reference colors compared against, each downloaded from a pinned URL and checked by SHA-256: the CSS named colors (W3C, CSS Color Module Level 4, CRD 2025-04-24; W3C Software and Document License, as linked from that document); the ColorChecker Classic's manufacturer sRGB values as tabulated in Wikipedia, "ColorChecker", revision 1367818471 (CC BY-SA 4.0); the ColorChecker's published X-Rite (before and after November 2014), GretagMacbeth 2005 and BabelColor average values as collected in colour-science 0.4.6 (BSD-3-Clause); RAL Classic approximate sRGB values as tabulated in Wikipedia, "List of RAL colours", revision 1369650571 (CC BY-SA 4.0); brand colors from Simple Icons 1.20.0, 2.19.0, 4.25.0, 9.21.0 and 16.36.0 (its data is dedicated to the public domain). Pantone data is proprietary and is not used. | Check-time use only: the script parses each source in memory, compares it with Photivra's own values and discards it. No third-party color value, name or table is stored in the repository, the package, the tests or the script's output, apart from what the script prints and `docs/COLOR_PATCHES.md` reports per patch as a factual result: the closest reference color's name, its set, its 8-bit sRGB hex (the "Its sRGB" column, one value per patch, 30 in all) and the ΔE00. The "Nudged patches" table there also names the reference colors the first design was too close to. `docs/COLOR_PATCHES.md` is repository documentation and is not in the npm package. The 30 hex values are attributed under its Result table: 7 ColorChecker and 6 RAL Classic values copied unchanged from the Wikipedia tables (CC BY-SA 4.0, credited with the title, authors, revision link and licence link that CC BY-SA 4.0 §3(a) asks for), 1 ColorChecker value converted to sRGB by the script from colour-science 0.4.6's CIELAB data (BSD-3-Clause, by the Colour Developers), 2 CSS named colors (W3C) and 14 Simple Icons brand colors (CC0 1.0). The patch names and colors are Photivra's own; the check only confirms that they differ. |
|
|
101
110
|
| Daylight-locus polynomial and M₁/M₂ coefficients; illuminant A formula constants; CIELAB, CIE94 and CIEDE2000 formula constants (`illuminants.ts`, `color-difference.ts`) | CIE 15:2018 (daylight and illuminant A constants; values checked against the free CIE datasets); CIELAB as in W3C CSS Color 4; CIE94 as stated in Wikipedia, "Color difference" (secondary source; the primary CIE 116-1995 is not free); Sharma, Wu & Dalal (2005), also CIE 142-2001 | Coefficients of standardized equations, used as factual constants. |
|
|
102
111
|
| McCamy CCT cubic | McCamy (1992), Color Res. Appl. 17(2) | Published equation coefficients. |
|
|
112
|
+
| CIE 1960 UCS (u, v) formulas: u = 4x/(−2x + 12y + 3), v = 6y/(−2x + 12y + 3) and their inverse (`colorimetry-math.ts`) | CIE 15 (the CIE 1960 UCS definition; paywalled, checked through the free secondary Wikipedia, "CIE 1960 color space") | Standard definitional formula constants (added to this inventory 2026-11-09, #544). |
|
|
113
|
+
| The 4-decimal sRGB → XYZ matrix in the GPU display-view test fixture (`src/gpu/display-view-quality-gpu.ts`, its `ahdCorrection.labMatrix`) | Derived from the ITU-R BT.709-6 primaries and the D65 white point (free); it equals the 4-decimal matrix of IEC 61966-2-1 | Standardized constants derived from free sources (added to this inventory 2026-11-09, #544). |
|
|
103
114
|
| Bradford and CAT16 matrices (`chromatic-adaptation.ts`) | Lam (1985) / ICC.1:2010 Annex E; CAT16: Li et al. (2016), IS&T CIC24, Eq. (15), doi:10.2352/ISSN.2169-2629.2017.32.208 (free; checked 2026-10-17 to match the engine; journal version Li et al. 2017) | Published 3×3 model constants. CAT02, the Hunt–Pointer–Estévez matrix and the CIECAM02 degree-of-adaptation formula were removed from 2.0 (owner decisions, 2026-10-17) because no confirmed free source prints them. |
|
|
104
115
|
| Primaries and white points; BT.709 and BT.1886 constants (`rgb-color-spaces.ts`) | ITU-R BT.709-6, BT.1886, BT.2020-2; W3C CSS Color 4 (`srgb`, `display-p3`); also IEC 61966-2-1, SMPTE EG 432-1 | Standardized numeric constants; every value is available from the freely published ITU-R and W3C documents. |
|
|
105
116
|
| h, c, k | BIPM SI Brochure (2019) | Exact SI defining constants (facts). |
|
|
@@ -148,14 +159,14 @@ The Malvar–He–Cutler demosaic kernels and their parameters, and the BT.2408
|
|
|
148
159
|
|
|
149
160
|
The original scalar adapter in `src/output/print-contrast-reference.ts` uses numerical equations/parameters from [castleCSF revision f4b0b722af83001d7af979281e06ca642d36e4e8](https://github.com/gfxdisp/castleCSF/tree/f4b0b722af83001d7af979281e06ca642d36e4e8): `matlab/CSF_castleCSF.m`, `CSF_stelaCSF_lum_peak.m`, `CSF_castleCSF_chrom.m` and `CSF_base.m`. The upstream code/model material is MIT-licensed, copyright (c) 2023 Graphics and Displays group - University of Cambridge. The exact license is preserved at `docs/licenses/castleCSF-MIT.txt` and its notice is retained in `NOTICE` and adapter source. New TypeScript/reference code is distributed under the project Apache-2.0 terms while retaining the MIT notice for upstream numerical material.
|
|
150
161
|
|
|
151
|
-
No upstream observational CSV, paper text/figure/table, color-matching dataset or runtime dependency is incorporated. The model's presence does not certify camera, print, observer or natural-image visibility applicability. [The model/protocol](https://github.com/Photivra/photivra/blob/main/docs/PRINT_CONTRAST_REFERENCE.md) records the restricted envelope, independent numerical evaluation and
|
|
162
|
+
No upstream observational CSV, paper text/figure/table, color-matching dataset or runtime dependency is incorporated. The model's presence does not certify camera, print, observer or natural-image visibility applicability. [The model/protocol](https://github.com/Photivra/photivra/blob/main/docs/PRINT_CONTRAST_REFERENCE.md) records the restricted envelope, independent numerical evaluation and the scientific/source/licence review the owner signed off on 2026-11-09 (#544). The root still declares no runtime npm dependencies.
|
|
152
163
|
|
|
153
164
|
|
|
154
165
|
## Optional offline physical-proof tools
|
|
155
166
|
|
|
156
167
|
The optional repository-only QA scripts `scripts/verify-photographic-export-fixtures.py` and `scripts/verify-producer-export-acceptance.py` load an installed LibRaw (LGPL-2.1 or CDDL-1.0) dynamically through its C API. Nothing from LibRaw is copied or distributed, and the scripts are not part of the package.
|
|
157
168
|
|
|
158
|
-
The owned PDF geometry reference is generated with ReportLab 4.4.9 and independently inspected with pypdf 6.10.0.
|
|
169
|
+
The owned PDF geometry reference is generated with ReportLab 4.4.9 and independently inspected with pypdf 6.10.0. The licences of the exact versions were checked on 2026-11-09: ReportLab 4.4.9 is BSD ("BSD license", ReportLab Inc.; PyPI metadata; the `LICENSE` text was read from an unofficial mirror, unchanged since 2024-12-17) and pypdf 6.10.0 is BSD-3-Clause (PyPI metadata and the `LICENSE` at the pypdf `6.10.0` tag). The PDF embeds no library code or font program, and neither licence sets conditions on output made with the software, so no notice is owed for the generated artifact. Pillow (a ReportLab dependency, used to read the JPEG header; the bytes pass through unchanged) was also present when it was generated; its version was not recorded. These are optional offline test-artifact tools, not runtime/npm dependencies. No library source, binary or embedded font program is vendored. The PDF preserves existing owned JPEG streams and uses the PDF standard Helvetica font declaration; its DeviceRGB appearance and actual printing remain unqualified. Generator/parser versions and artifact hashes are retained in the candidate evidence record. The owner signed off this review on 2026-11-09 (#544).
|
|
159
170
|
|
|
160
171
|
## Automated license policy
|
|
161
172
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { prepareDevelopedLensCorrection, type ChannelDispersion, type DevelopedLensCorrectionSelection } from "../output/developed-lens-corrections.js";
|
|
2
|
+
/** @internal An opcode as written (application order), for the assumptions. */
|
|
3
|
+
export type DngLensCorrectionOpcode =
|
|
4
|
+
/** k0…k4 of g = 1 + k0r² + … + k4r¹⁰, and the largest |g/G − 1| on 2 048 radii in [0, 1]. */
|
|
5
|
+
{
|
|
6
|
+
opcode: "FixVignetteRadial";
|
|
7
|
+
coefficients: number[];
|
|
8
|
+
maximumRelativeFitError: number;
|
|
9
|
+
}
|
|
10
|
+
/** kr0…kr3, kt0, kt1 of one plane (all channels) or red, green, blue; the zoom-to-fill 1/m folded into kr0. */
|
|
11
|
+
| {
|
|
12
|
+
opcode: "WarpRectilinear";
|
|
13
|
+
planes: number[][];
|
|
14
|
+
magnification: number;
|
|
15
|
+
};
|
|
16
|
+
/** @internal Largest accepted |g/G − 1| of the FixVignetteRadial fit. */
|
|
17
|
+
export declare const DNG_VIGNETTE_FIT_TOLERANCE = 0.001;
|
|
18
|
+
/**
|
|
19
|
+
* @internal The OpcodeList3 bytes and the opcodes for a catalogue lens (`resolveGenericLens` at the capture's f-number, on the
|
|
20
|
+
* W × H ActiveArea of pitch `pitchMm`) and the selected corrections, at least one on (see the module notes).
|
|
21
|
+
*/
|
|
22
|
+
export declare function createDngLensCorrectionOpcodes(lens: Parameters<typeof prepareDevelopedLensCorrection>[0] & {
|
|
23
|
+
name: string;
|
|
24
|
+
character: {
|
|
25
|
+
vignetting: {
|
|
26
|
+
cornerStops: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}, selection: DevelopedLensCorrectionSelection, width: number, height: number, pitchMm: number, channels: ChannelDispersion): {
|
|
30
|
+
opcodeList3: Uint8Array;
|
|
31
|
+
opcodes: DngLensCorrectionOpcode[];
|
|
32
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// This product includes DNG technology under license by Adobe.
|
|
3
|
+
/**
|
|
4
|
+
* @internal #587: a catalogue lens's generic corrections (`createGenericLensCorrectionProfile`, the same arithmetic as `output.lensCorrections`)
|
|
5
|
+
* written as a DNG opcode list, so a RAW editor that applies DNG opcodes corrects the DNG non-destructively; the RAW codes are never
|
|
6
|
+
* changed. Written from the Adobe DNG Specification (opcode lists were added in version 1.3.0.0; the repository's writer follows
|
|
7
|
+
* 1.7.1.0), chapter "Opcode List Processing":
|
|
8
|
+
*
|
|
9
|
+
* - The list goes into OpcodeList3 (tag 51022, UNDEFINED), which a reader applies to the linear, demosaiced stage-3 image: the
|
|
10
|
+
* ActiveArea, which for `simulateCapture` is exactly the output frame. The list is big-endian whatever the file's byte order: a
|
|
11
|
+
* LONG opcode count, then per opcode its LONG id, the four-byte DNG version that introduced it (1.3.0.0), LONG flags and the LONG
|
|
12
|
+
* byte count of its parameters. Every opcode is written with flag bit 0 ("optional") set, so a reader that does not support it may
|
|
13
|
+
* skip it, and bit 1 (preview skip) clear.
|
|
14
|
+
* - FixVignetteRadial (id 3) first, when vignetting is on: parameters k0…k4, cx, cy (DOUBLE); each pixel is multiplied by
|
|
15
|
+
* g = 1 + k0r² + k1r⁴ + k2r⁶ + k3r⁸ + k4r¹⁰, r the distance from the optical centre (cx, cy) normalized to 1 at the image's
|
|
16
|
+
* farthest corner. It runs before the warp, so it acts at the captured (source) position, where the capture applied the falloff.
|
|
17
|
+
* The target gain is the profile's (#584): G(ρ) = det J(ρ_i)/E(ρ), E = (1 − bρ²)² the lens's relative illumination at the
|
|
18
|
+
* capture's f-number and det J = (1 + k1ρ_i²)(1 + 3k1ρ_i²) at the ideal radius ρ_i with ρ_i(1 + k1ρ_i²) = ρ (the lens's own k1,
|
|
19
|
+
* whether or not distortion is corrected). G is not a polynomial in r², so k0…k4 are a near-minimax fit of g/G − 1 (Lawson's
|
|
20
|
+
* iteratively reweighted least squares) on 2 048 radii in [0, 1]; its largest |g/G − 1| there is reported, and a fit worse than
|
|
21
|
+
* 0.1 % is refused (every catalogue lens fits within 0.07 %; GainMap, id 9, would be needed otherwise).
|
|
22
|
+
* - WarpRectilinear (id 1) then, when distortion or lateral colour is on: N = 1 plane (no lateral colour) or 3 planes (red,
|
|
23
|
+
* green, blue; CFAPlaneColor 0 1 2), each kr0…kr3, kt0, kt1, then cx, cy (DOUBLE). For a destination pixel at normalized
|
|
24
|
+
* offset (dx, dy) from the centre, r² = dx² + dy², the source is the centre plus (dx, dy)·(kr0 + kr1r² + kr2r⁴ + kr3r⁶) plus the
|
|
25
|
+
* tangential terms (kt0 = kt1 = 0 here). `output.lensCorrections` reads channel c at s = q(1 + k1|q|²/R²), q = m·a_c·p (R the
|
|
26
|
+
* half-diagonal, a_c the channel's lateral-colour scale, m the zoom-to-fill factor): so kr0 = m·a_c, kr1 = k1·(m·a_c)³·(R_DNG/R)²,
|
|
27
|
+
* kr2 = kr3 = 0 represent it exactly (R_DNG the centre-to-corner distance of the stage-3 image; equal to R for `simulateCapture`).
|
|
28
|
+
* The zoom-to-fill of #570 is folded into kr0, so an editor's result has the corrected JPEG's framing.
|
|
29
|
+
* - The optical centre is the image centre, cx = cy = 0.5 (0 the left or top edge, 1 the right or bottom edge).
|
|
30
|
+
*
|
|
31
|
+
* Limits: the gain is an approximation fitted to the profile (itself a labelled catalogue approximation); a reader's resampling
|
|
32
|
+
* filter, and whether it applies opcodes at all, is the reader's. Some editors apply DNG opcodes automatically with no control in
|
|
33
|
+
* the editor: the switch that is guaranteed is leaving a correction out of the DNG.
|
|
34
|
+
*/
|
|
35
|
+
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
36
|
+
import { prepareDevelopedLensCorrection } from "../output/developed-lens-corrections.js";
|
|
37
|
+
/** @internal Largest accepted |g/G − 1| of the FixVignetteRadial fit. */
|
|
38
|
+
export const DNG_VIGNETTE_FIT_TOLERANCE = 1e-3;
|
|
39
|
+
/**
|
|
40
|
+
* @internal The OpcodeList3 bytes and the opcodes for a catalogue lens (`resolveGenericLens` at the capture's f-number, on the
|
|
41
|
+
* W × H ActiveArea of pitch `pitchMm`) and the selected corrections, at least one on (see the module notes).
|
|
42
|
+
*/
|
|
43
|
+
export function createDngLensCorrectionOpcodes(lens, selection, width, height, pitchMm, channels) {
|
|
44
|
+
const opcodes = [], params = [];
|
|
45
|
+
// R_DNG / R: the stage-3 image's centre-to-corner distance over the profile's half-diagonal (1 when the frame is the lens's area).
|
|
46
|
+
const scale = Math.hypot(width, height) / 2 / (lens.halfDiagonalMm / pitchMm);
|
|
47
|
+
if (selection.vignetting) {
|
|
48
|
+
const b = 1 - 2 ** (-lens.character.vignetting.cornerStops / 2), k1 = lens.character.distortion.corner;
|
|
49
|
+
const target = (r) => {
|
|
50
|
+
const rho = r * scale, e = 1 - b * rho * rho;
|
|
51
|
+
let ri = rho;
|
|
52
|
+
for (let n = 0; n < 8; n++)
|
|
53
|
+
ri -= (ri * (1 + k1 * ri * ri) - rho) / (1 + 3 * k1 * ri * ri);
|
|
54
|
+
return (1 + k1 * ri * ri) * (1 + 3 * k1 * ri * ri) / (e * e);
|
|
55
|
+
};
|
|
56
|
+
// Near-minimax fit of the relative error g/G − 1 (t = r²: rows t^j/G, j = 1…5, against 1 − 1/G) by Lawson's iteratively
|
|
57
|
+
// reweighted least squares on 2 048 radii in [0, 1] (normal equations, positive definite); of 64 iterations the one with the
|
|
58
|
+
// smallest largest error on those radii is kept.
|
|
59
|
+
const radii = Array.from({ length: 2048 }, (_, i) => { const r = i / 2047; return [r * r, target(r), 1]; });
|
|
60
|
+
let best = [0, 0, 0, 0, 0], worst = Infinity;
|
|
61
|
+
for (let iteration = 0; iteration < 64; iteration++) {
|
|
62
|
+
const a = Array.from({ length: 5 }, () => [0, 0, 0, 0, 0, 0]), k = [0, 0, 0, 0, 0];
|
|
63
|
+
for (const [t, G, w] of radii) {
|
|
64
|
+
const row = [t, t ** 2, t ** 3, t ** 4, t ** 5, G - 1].map((v) => v / G);
|
|
65
|
+
for (let j = 0; j < 5; j++)
|
|
66
|
+
for (let m = 0; m < 6; m++)
|
|
67
|
+
a[j][m] += w * row[j] * row[m];
|
|
68
|
+
}
|
|
69
|
+
for (let j = 0; j < 5; j++)
|
|
70
|
+
for (let i = j + 1; i < 5; i++) {
|
|
71
|
+
const f = a[i][j] / a[j][j];
|
|
72
|
+
for (let m = j; m < 6; m++)
|
|
73
|
+
a[i][m] -= f * a[j][m];
|
|
74
|
+
}
|
|
75
|
+
for (let j = 4; j >= 0; j--)
|
|
76
|
+
k[j] = (a[j][5] - a[j].slice(j + 1, 5).reduce((sum, v, m) => sum + v * k[j + 1 + m], 0)) / a[j][j];
|
|
77
|
+
const errors = radii.map(([t, G]) => Math.abs((1 + t * (k[0] + t * (k[1] + t * (k[2] + t * (k[3] + t * k[4]))))) / G - 1)), max = Math.max(...errors);
|
|
78
|
+
if (max < worst) {
|
|
79
|
+
worst = max;
|
|
80
|
+
best = k;
|
|
81
|
+
}
|
|
82
|
+
radii.forEach((p, i) => { p[2] = p[2] * (errors[i] / max + 1e-9); });
|
|
83
|
+
}
|
|
84
|
+
if (!(worst <= DNG_VIGNETTE_FIT_TOLERANCE))
|
|
85
|
+
throw new InvalidConfigurationError("output.dng.lensCorrectionOpcodes: FixVignetteRadial cannot hold " + lens.name + "'s vignetting gain within 0.1 %.");
|
|
86
|
+
opcodes.push({ opcode: "FixVignetteRadial", coefficients: best, maximumRelativeFitError: worst });
|
|
87
|
+
params.push([...best, 0.5, 0.5]);
|
|
88
|
+
}
|
|
89
|
+
if (selection.distortion || selection.lateralColor) {
|
|
90
|
+
const c = prepareDevelopedLensCorrection(lens, { ...selection, vignetting: false }, width, height, pitchMm, channels);
|
|
91
|
+
const planes = (selection.lateralColor ? c.scales : [1]).map((s) => { const a = c.fill * s; return [a, c.k1 * a ** 3 * scale * scale, 0, 0, 0, 0]; });
|
|
92
|
+
opcodes.push({ opcode: "WarpRectilinear", planes, magnification: 1 / c.fill });
|
|
93
|
+
params.push([planes.length, ...planes.flat(), 0.5, 0.5]);
|
|
94
|
+
}
|
|
95
|
+
// Big-endian: the count, then per opcode its id, DNG version 1.3.0.0, flags 1 (optional), parameter bytes and parameters
|
|
96
|
+
// (all DOUBLE but WarpRectilinear's leading LONG plane count).
|
|
97
|
+
const size = (i) => params[i].length * 8 - (opcodes[i].opcode === "WarpRectilinear" ? 4 : 0);
|
|
98
|
+
const bytes = new Uint8Array(4 + params.reduce((sum, _, i) => sum + 16 + size(i), 0)), view = new DataView(bytes.buffer);
|
|
99
|
+
view.setUint32(0, opcodes.length);
|
|
100
|
+
let at = 4;
|
|
101
|
+
params.forEach((values, i) => {
|
|
102
|
+
const warp = opcodes[i].opcode === "WarpRectilinear";
|
|
103
|
+
view.setUint32(at, warp ? 1 : 3);
|
|
104
|
+
view.setUint32(at + 4, 0x01030000);
|
|
105
|
+
view.setUint32(at + 8, 1);
|
|
106
|
+
view.setUint32(at + 12, size(i));
|
|
107
|
+
at += 16;
|
|
108
|
+
values.forEach((v, j) => { if (warp && j === 0) {
|
|
109
|
+
view.setUint32(at, v);
|
|
110
|
+
at += 4;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
view.setFloat64(at, v);
|
|
114
|
+
at += 8;
|
|
115
|
+
} });
|
|
116
|
+
});
|
|
117
|
+
return { opcodeList3: bytes, opcodes };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=dng-lens-correction-opcodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dng-lens-correction-opcodes.js","sourceRoot":"","sources":["../../src/capture/dng-lens-correction-opcodes.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n// This product includes DNG technology under license by Adobe.\n\n/**\n * @internal #587: a catalogue lens's generic corrections (`createGenericLensCorrectionProfile`, the same arithmetic as `output.lensCorrections`)\n * written as a DNG opcode list, so a RAW editor that applies DNG opcodes corrects the DNG non-destructively; the RAW codes are never\n * changed. Written from the Adobe DNG Specification (opcode lists were added in version 1.3.0.0; the repository's writer follows\n * 1.7.1.0), chapter \"Opcode List Processing\":\n *\n * - The list goes into OpcodeList3 (tag 51022, UNDEFINED), which a reader applies to the linear, demosaiced stage-3 image: the\n * ActiveArea, which for `simulateCapture` is exactly the output frame. The list is big-endian whatever the file's byte order: a\n * LONG opcode count, then per opcode its LONG id, the four-byte DNG version that introduced it (1.3.0.0), LONG flags and the LONG\n * byte count of its parameters. Every opcode is written with flag bit 0 (\"optional\") set, so a reader that does not support it may\n * skip it, and bit 1 (preview skip) clear.\n * - FixVignetteRadial (id 3) first, when vignetting is on: parameters k0…k4, cx, cy (DOUBLE); each pixel is multiplied by\n * g = 1 + k0r² + k1r⁴ + k2r⁶ + k3r⁸ + k4r¹⁰, r the distance from the optical centre (cx, cy) normalized to 1 at the image's\n * farthest corner. It runs before the warp, so it acts at the captured (source) position, where the capture applied the falloff.\n * The target gain is the profile's (#584): G(ρ) = det J(ρ_i)/E(ρ), E = (1 − bρ²)² the lens's relative illumination at the\n * capture's f-number and det J = (1 + k1ρ_i²)(1 + 3k1ρ_i²) at the ideal radius ρ_i with ρ_i(1 + k1ρ_i²) = ρ (the lens's own k1,\n * whether or not distortion is corrected). G is not a polynomial in r², so k0…k4 are a near-minimax fit of g/G − 1 (Lawson's\n * iteratively reweighted least squares) on 2 048 radii in [0, 1]; its largest |g/G − 1| there is reported, and a fit worse than\n * 0.1 % is refused (every catalogue lens fits within 0.07 %; GainMap, id 9, would be needed otherwise).\n * - WarpRectilinear (id 1) then, when distortion or lateral colour is on: N = 1 plane (no lateral colour) or 3 planes (red,\n * green, blue; CFAPlaneColor 0 1 2), each kr0…kr3, kt0, kt1, then cx, cy (DOUBLE). For a destination pixel at normalized\n * offset (dx, dy) from the centre, r² = dx² + dy², the source is the centre plus (dx, dy)·(kr0 + kr1r² + kr2r⁴ + kr3r⁶) plus the\n * tangential terms (kt0 = kt1 = 0 here). `output.lensCorrections` reads channel c at s = q(1 + k1|q|²/R²), q = m·a_c·p (R the\n * half-diagonal, a_c the channel's lateral-colour scale, m the zoom-to-fill factor): so kr0 = m·a_c, kr1 = k1·(m·a_c)³·(R_DNG/R)²,\n * kr2 = kr3 = 0 represent it exactly (R_DNG the centre-to-corner distance of the stage-3 image; equal to R for `simulateCapture`).\n * The zoom-to-fill of #570 is folded into kr0, so an editor's result has the corrected JPEG's framing.\n * - The optical centre is the image centre, cx = cy = 0.5 (0 the left or top edge, 1 the right or bottom edge).\n *\n * Limits: the gain is an approximation fitted to the profile (itself a labelled catalogue approximation); a reader's resampling\n * filter, and whether it applies opcodes at all, is the reader's. Some editors apply DNG opcodes automatically with no control in\n * the editor: the switch that is guaranteed is leaving a correction out of the DNG.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { prepareDevelopedLensCorrection, type ChannelDispersion, type DevelopedLensCorrectionSelection } from \"../output/developed-lens-corrections.js\";\n\n/** @internal An opcode as written (application order), for the assumptions. */\nexport type DngLensCorrectionOpcode =\n /** k0…k4 of g = 1 + k0r² + … + k4r¹⁰, and the largest |g/G − 1| on 2 048 radii in [0, 1]. */\n | { opcode: \"FixVignetteRadial\"; coefficients: number[]; maximumRelativeFitError: number }\n /** kr0…kr3, kt0, kt1 of one plane (all channels) or red, green, blue; the zoom-to-fill 1/m folded into kr0. */\n | { opcode: \"WarpRectilinear\"; planes: number[][]; magnification: number };\n\n/** @internal Largest accepted |g/G − 1| of the FixVignetteRadial fit. */\nexport const DNG_VIGNETTE_FIT_TOLERANCE = 1e-3;\n\n/**\n * @internal The OpcodeList3 bytes and the opcodes for a catalogue lens (`resolveGenericLens` at the capture's f-number, on the\n * W × H ActiveArea of pitch `pitchMm`) and the selected corrections, at least one on (see the module notes).\n */\nexport function createDngLensCorrectionOpcodes(lens: Parameters<typeof prepareDevelopedLensCorrection>[0] & { name: string; character: { vignetting: { cornerStops: number } } },\n selection: DevelopedLensCorrectionSelection, width: number, height: number, pitchMm: number, channels: ChannelDispersion): { opcodeList3: Uint8Array; opcodes: DngLensCorrectionOpcode[] } {\n const opcodes: DngLensCorrectionOpcode[] = [], params: number[][] = [];\n // R_DNG / R: the stage-3 image's centre-to-corner distance over the profile's half-diagonal (1 when the frame is the lens's area).\n const scale = Math.hypot(width, height) / 2 / (lens.halfDiagonalMm / pitchMm);\n if (selection.vignetting) {\n const b = 1 - 2 ** (-lens.character.vignetting.cornerStops / 2), k1 = lens.character.distortion.corner;\n const target = (r: number): number => {\n const rho = r * scale, e = 1 - b * rho * rho;\n let ri = rho;\n for (let n = 0; n < 8; n++) ri -= (ri * (1 + k1 * ri * ri) - rho) / (1 + 3 * k1 * ri * ri);\n return (1 + k1 * ri * ri) * (1 + 3 * k1 * ri * ri) / (e * e);\n };\n // Near-minimax fit of the relative error g/G − 1 (t = r²: rows t^j/G, j = 1…5, against 1 − 1/G) by Lawson's iteratively\n // reweighted least squares on 2 048 radii in [0, 1] (normal equations, positive definite); of 64 iterations the one with the\n // smallest largest error on those radii is kept.\n const radii = Array.from({ length: 2048 }, (_, i) => { const r = i / 2047; return [r * r, target(r), 1]; });\n let best = [0, 0, 0, 0, 0], worst = Infinity;\n for (let iteration = 0; iteration < 64; iteration++) {\n const a = Array.from({ length: 5 }, () => [0, 0, 0, 0, 0, 0]), k = [0, 0, 0, 0, 0];\n for (const [t, G, w] of radii) {\n const row = [t!, t! ** 2, t! ** 3, t! ** 4, t! ** 5, G! - 1].map((v) => v / G!);\n for (let j = 0; j < 5; j++) for (let m = 0; m < 6; m++) a[j]![m]! += w! * row[j]! * row[m]!;\n }\n for (let j = 0; j < 5; j++) for (let i = j + 1; i < 5; i++) { const f = a[i]![j]! / a[j]![j]!; for (let m = j; m < 6; m++) a[i]![m]! -= f * a[j]![m]!; }\n for (let j = 4; j >= 0; j--) k[j] = (a[j]![5]! - a[j]!.slice(j + 1, 5).reduce((sum, v, m) => sum + v * k[j + 1 + m]!, 0)) / a[j]![j]!;\n const errors = radii.map(([t, G]) => Math.abs((1 + t! * (k[0]! + t! * (k[1]! + t! * (k[2]! + t! * (k[3]! + t! * k[4]!))))) / G! - 1)), max = Math.max(...errors);\n if (max < worst) { worst = max; best = k; }\n radii.forEach((p, i) => { p[2] = p[2]! * (errors[i]! / max + 1e-9); });\n }\n if (!(worst <= DNG_VIGNETTE_FIT_TOLERANCE)) throw new InvalidConfigurationError(\"output.dng.lensCorrectionOpcodes: FixVignetteRadial cannot hold \" + lens.name + \"'s vignetting gain within 0.1 %.\");\n opcodes.push({ opcode: \"FixVignetteRadial\", coefficients: best, maximumRelativeFitError: worst });\n params.push([...best, 0.5, 0.5]);\n }\n if (selection.distortion || selection.lateralColor) {\n const c = prepareDevelopedLensCorrection(lens, { ...selection, vignetting: false }, width, height, pitchMm, channels);\n const planes = (selection.lateralColor ? c.scales : [1]).map((s) => { const a = c.fill * s; return [a, c.k1 * a ** 3 * scale * scale, 0, 0, 0, 0]; });\n opcodes.push({ opcode: \"WarpRectilinear\", planes, magnification: 1 / c.fill });\n params.push([planes.length, ...planes.flat(), 0.5, 0.5]);\n }\n // Big-endian: the count, then per opcode its id, DNG version 1.3.0.0, flags 1 (optional), parameter bytes and parameters\n // (all DOUBLE but WarpRectilinear's leading LONG plane count).\n const size = (i: number): number => params[i]!.length * 8 - (opcodes[i]!.opcode === \"WarpRectilinear\" ? 4 : 0);\n const bytes = new Uint8Array(4 + params.reduce((sum, _, i) => sum + 16 + size(i), 0)), view = new DataView(bytes.buffer);\n view.setUint32(0, opcodes.length);\n let at = 4;\n params.forEach((values, i) => {\n const warp = opcodes[i]!.opcode === \"WarpRectilinear\";\n view.setUint32(at, warp ? 1 : 3); view.setUint32(at + 4, 0x01030000); view.setUint32(at + 8, 1); view.setUint32(at + 12, size(i)); at += 16;\n values.forEach((v, j) => { if (warp && j === 0) { view.setUint32(at, v); at += 4; } else { view.setFloat64(at, v); at += 8; } });\n });\n return { opcodeList3: bytes, opcodes };\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal (#534): the sensor-stage hook of one render of an exposure build-up (`simulateExposureBuildUp`). After a site's
|
|
3
|
+
* ordinary photo and dark draws, `split` gets the render's expected means and those draws, and sets the counts the readout
|
|
4
|
+
* uses (`photo`, `dark`); `read` names the readout's read-noise stream. The build-up's own logic (binomial splitting, the
|
|
5
|
+
* per-site chain) lives in facade/exposure-build-up.ts, so captures without a build-up carry only this registry.
|
|
6
|
+
*/
|
|
7
|
+
export interface ExposureBuildUpStage {
|
|
8
|
+
/** Sets `photo` and `dark` for the absolute native site `index` from this render's expected means and ordinary draws. */
|
|
9
|
+
split(index: number, photoMean: number, darkMean: number, photo: number, dark: number): void;
|
|
10
|
+
photo: number;
|
|
11
|
+
dark: number;
|
|
12
|
+
/** The read-noise stream ID of the readout (`deriveNativeSiteStreamSeedUint32`). */
|
|
13
|
+
read: number;
|
|
14
|
+
}
|
|
15
|
+
/** The running build-up renders' stages, by their native RAW input (`NativeRawInput`, the object the capture facade builds). */
|
|
16
|
+
export declare const EXPOSURE_BUILD_UP_STAGES: WeakMap<object, ExposureBuildUpStage>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/** The running build-up renders' stages, by their native RAW input (`NativeRawInput`, the object the capture facade builds). */
|
|
3
|
+
export const EXPOSURE_BUILD_UP_STAGES = new WeakMap();
|
|
4
|
+
//# sourceMappingURL=exposure-build-up-stage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exposure-build-up-stage.js","sourceRoot":"","sources":["../../src/capture/exposure-build-up-stage.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAkBtC,gIAAgI;AAChI,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,OAAO,EAAgC,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#534): the sensor-stage hook of one render of an exposure build-up (`simulateExposureBuildUp`). After a site's\n * ordinary photo and dark draws, `split` gets the render's expected means and those draws, and sets the counts the readout\n * uses (`photo`, `dark`); `read` names the readout's read-noise stream. The build-up's own logic (binomial splitting, the\n * per-site chain) lives in facade/exposure-build-up.ts, so captures without a build-up carry only this registry.\n */\n\nexport interface ExposureBuildUpStage {\n /** Sets `photo` and `dark` for the absolute native site `index` from this render's expected means and ordinary draws. */\n split(index: number, photoMean: number, darkMean: number, photo: number, dark: number): void;\n photo: number;\n dark: number;\n /** The read-noise stream ID of the readout (`deriveNativeSiteStreamSeedUint32`). */\n read: number;\n}\n\n/** The running build-up renders' stages, by their native RAW input (`NativeRawInput`, the object the capture facade builds). */\nexport const EXPOSURE_BUILD_UP_STAGES = new WeakMap<object, ExposureBuildUpStage>();\n"]}
|
|
@@ -41,6 +41,7 @@ import { parseFocusPlane } from "../optics/focus-state.js";
|
|
|
41
41
|
import { prepareCaptureExposureWindowSampler } from "../sensor/exposure-window.js";
|
|
42
42
|
import { calculateNativeRawSite, type NativeRawProducerFrameContext, type SensorRawProducerSiteInput } from "./sensor-raw-producer.js";
|
|
43
43
|
import type { NativeRawLeanOutput, NativeRawLeanSensorHook } from "./native-raw.js";
|
|
44
|
+
import type { ExposureBuildUpStage } from "./exposure-build-up-stage.js";
|
|
44
45
|
type Site = SimulateEnvironmentSensorRawFrameInput["sites"][number];
|
|
45
46
|
type Env = Site["environment"];
|
|
46
47
|
type FullPlan = ReturnType<typeof planEnvironmentSensorPhotoSignal>;
|
|
@@ -230,7 +231,7 @@ export interface NativeSiteReadout {
|
|
|
230
231
|
* its codes and its flags bit for bit. Its `compute` then gives `calculateNativeRawSite`'s outputs for any site of the same
|
|
231
232
|
* readout inputs (same CFA channel, dark current, capacity, readout regime and fixed-pattern profile).
|
|
232
233
|
*/
|
|
233
|
-
export declare function createVerifiedNativeSiteReadout(parsed: SensorRawProducerSiteInput, result: ReturnType<typeof calculateNativeRawSite>, whiteCode: number, photo: SensorEqeTemporalPhotoSignal, x: number, y: number, index: number, seed: number): NativeSiteReadout | undefined;
|
|
234
|
+
export declare function createVerifiedNativeSiteReadout(parsed: SensorRawProducerSiteInput, result: ReturnType<typeof calculateNativeRawSite>, whiteCode: number, photo: SensorEqeTemporalPhotoSignal, x: number, y: number, index: number, seed: number, b?: ExposureBuildUpStage): NativeSiteReadout | undefined;
|
|
234
235
|
/**
|
|
235
236
|
* Lean sensor stage for createNativeEnvironmentRawTask.
|
|
236
237
|
*
|
|
@@ -24,10 +24,10 @@ import { calculateSiteEffectiveSaturationCode } from "./raw-white-level.js";
|
|
|
24
24
|
import { adcTransfer, resolveSensorReadoutRegime, samplePoisson } from "../sensor/raw-readout.js";
|
|
25
25
|
import { createResettableUniformStream, deriveNativeSiteStreamSeedUint32, sampleDeterministicStandardNormal, NATIVE_SITE_CHARGE_STREAM_ID, NATIVE_SITE_READ_NOISE_STREAM_ID } from "../sensor/stochastic-seed.js";
|
|
26
26
|
import { evaluateParsedFixedPatternSite } from "../sensor/fixed-pattern-noise.js";
|
|
27
|
+
import { approximatelyEqual } from "../core/shared-helpers.js";
|
|
27
28
|
/** Most distinct templates kept for one capture event. Any further distinct sites just run the full path. */
|
|
28
29
|
const MAXIMUM_TEMPLATES = 64;
|
|
29
30
|
const SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;
|
|
30
|
-
const REDUCTION_TOLERANCE = 1e-10;
|
|
31
31
|
/** Disables the lean executor process-wide (internal; tests compare both paths). */
|
|
32
32
|
let leanDisabled = false;
|
|
33
33
|
/** Internal counters of lean work (tests assert the lean path actually runs). Not exported from the package. */
|
|
@@ -882,10 +882,6 @@ export function createNativeEnvironmentLeanExecutor(frame, access) {
|
|
|
882
882
|
}
|
|
883
883
|
};
|
|
884
884
|
}
|
|
885
|
-
function approximatelyEqual(actual, expected) {
|
|
886
|
-
const scale = Math.max(1, Math.abs(actual), Math.abs(expected));
|
|
887
|
-
return Math.abs(actual - expected) <= REDUCTION_TOLERANCE * scale;
|
|
888
|
-
}
|
|
889
885
|
/** Photo-signal fields that vary per site; every other field is template-level. */
|
|
890
886
|
const PHOTO_SITE_FIELDS = new Set(["temporalIntegrationId", "site", "startOffsetSecondsFromOpeningReference",
|
|
891
887
|
"endOffsetSecondsFromOpeningReference", "localExposureDurationSeconds", "expectedIncidentPhotonCount", "expectedGeneratedElectronCount", "temporalSamples"]);
|
|
@@ -969,58 +965,21 @@ function sensorMatches(site, photo, x, y, t, memo) {
|
|
|
969
965
|
return sameRecordExcept(photo, t.photo, PHOTO_SITE_FIELDS, memo) && siteSlot(photo.site, x, y) &&
|
|
970
966
|
photo.temporalSamples.length === t.photo.temporalSamples.length;
|
|
971
967
|
}
|
|
972
|
-
/** The sensor stage of calculateNativeRawSite, in the same operation order. Returns undefined when one of its checks would throw. */
|
|
973
968
|
/**
|
|
974
|
-
* #418: the charge and read-noise streams of the site
|
|
969
|
+
* #418: the charge and read-noise streams of the site flatSensorSite is computing, reseeded for each site. flatSensorSite is
|
|
975
970
|
* synchronous and never re-entered, so one pair serves every call; reseeding sets exactly the state a new
|
|
976
971
|
* createDeterministicUniformStream would start from, so every draw is unchanged.
|
|
977
972
|
*/
|
|
978
973
|
const chargeStream = createResettableUniformStream(), readNoiseStream = createResettableUniformStream();
|
|
979
|
-
function
|
|
980
|
-
const ratio = h.total / t.capacity, headroom = t.capacity - h.total;
|
|
981
|
-
if (!Number.isFinite(ratio) || !Number.isFinite(headroom))
|
|
982
|
-
return undefined;
|
|
983
|
-
const pattern = t.pattern === undefined ? null : evaluateParsedFixedPatternSite(t.pattern, { x: h.x, y: h.y });
|
|
984
|
-
const photoE = h.photo.expectedGeneratedElectronCount;
|
|
985
|
-
const photoMean = pattern === null ? photoE : photoE * pattern.photoResponseGainFactor;
|
|
986
|
-
const darkMean = pattern === null ? h.darkE : h.darkE * pattern.darkCurrentFactor;
|
|
987
|
-
if (!Number.isFinite(photoMean) || photoMean < 0 || !Number.isFinite(darkMean) || darkMean < 0)
|
|
988
|
-
return undefined;
|
|
989
|
-
const random = chargeStream;
|
|
990
|
-
random.reset(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_CHARGE_STREAM_ID));
|
|
991
|
-
const photoRealized = samplePoisson(photoMean, random), darkRealized = samplePoisson(darkMean, random);
|
|
992
|
-
const realized = photoRealized + darkRealized + 0;
|
|
993
|
-
if (!Number.isFinite(realized) || realized < 0)
|
|
994
|
-
return undefined;
|
|
995
|
-
const readRandom = readNoiseStream;
|
|
996
|
-
readRandom.reset(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_READ_NOISE_STREAM_ID));
|
|
997
|
-
const regime = t.readout.regime, capacity = t.capacity;
|
|
998
|
-
const afterPhysical = Math.min(realized, capacity), physicalSaturation = afterPhysical < realized;
|
|
999
|
-
// The components' draws and their sum in one loop: the same draws, added to 0 in the same order as before (#418).
|
|
1000
|
-
const components = regime.readNoiseComponents;
|
|
1001
|
-
let totalReadNoise = 0;
|
|
1002
|
-
for (let k = 0; k < components.length; k++) {
|
|
1003
|
-
const rms = components[k].rmsElectrons.value;
|
|
1004
|
-
totalReadNoise = totalReadNoise + (rms === 0 ? 0 : sampleDeterministicStandardNormal(readRandom) * rms);
|
|
1005
|
-
}
|
|
1006
|
-
const afterReadNoise = afterPhysical + totalReadNoise + (pattern === null ? 0 : pattern.readoutOffsetElectrons);
|
|
1007
|
-
const threshold = regime.preAdcSaturationElectronEquivalent.value;
|
|
1008
|
-
const afterPreAdc = Math.min(afterReadNoise, threshold), preAdcSaturation = afterReadNoise > threshold;
|
|
1009
|
-
const quantized = adcTransfer(regime, t.readout.adcMaximumCode, afterPreAdc).quantized;
|
|
1010
|
-
const digitalSaturationCode = regime.adc.digitalSaturationCode;
|
|
1011
|
-
return { rawCode: Math.max(0, Math.min(quantized, digitalSaturationCode)), blackLevelCode: regime.adc.blackLevelCode, digitalSaturationCode,
|
|
1012
|
-
flags: Number(physicalSaturation) | Number(preAdcSaturation) << 1 | Number(quantized >= digitalSaturationCode) << 2,
|
|
1013
|
-
whiteCode: t.whiteCode, photoRealized, darkRealized };
|
|
1014
|
-
}
|
|
1015
|
-
function flatSensorStage(t) {
|
|
974
|
+
function flatSensorStage(t, b) {
|
|
1016
975
|
const regime = t.readout.regime;
|
|
1017
976
|
return { darkRate: t.darkRate, capacity: t.capacity, pattern: t.pattern, whiteCode: t.whiteCode, regime, adcMaximumCode: t.readout.adcMaximumCode,
|
|
1018
977
|
inlDefined: regime.adc.integralNonLinearity !== undefined, gain: regime.systemConversionGainElectronsPerCode.value, black: regime.adc.blackLevelCode,
|
|
1019
978
|
digitalGain: regime.adc.digitalGain, digitalSaturationCode: regime.adc.digitalSaturationCode, threshold: regime.preAdcSaturationElectronEquivalent.value,
|
|
1020
979
|
rms: Float64Array.from(regime.readNoiseComponents, (component) => component.rmsElectrons.value), darkMean: NaN, darkThreshold: NaN,
|
|
1021
|
-
out: { rawCode: 0, blackLevelCode: 0, digitalSaturationCode: 0, flags: 0, whiteCode: 0 } };
|
|
980
|
+
out: { rawCode: 0, blackLevelCode: 0, digitalSaturationCode: 0, flags: 0, whiteCode: 0, photoRealized: 0, darkRealized: 0 }, b };
|
|
1022
981
|
}
|
|
1023
|
-
/** Writes the site's outputs into `f.out`; false where
|
|
982
|
+
/** Writes the site's outputs into `f.out`; false where a check of calculateNativeRawSite would throw. */
|
|
1024
983
|
function flatSensorSite(f, photoE, darkE, total, x, y, index, seed) {
|
|
1025
984
|
const ratio = total / f.capacity, headroom = f.capacity - total;
|
|
1026
985
|
if (!Number.isFinite(ratio) || !Number.isFinite(headroom))
|
|
@@ -1032,8 +991,7 @@ function flatSensorSite(f, photoE, darkE, total, x, y, index, seed) {
|
|
|
1032
991
|
return false;
|
|
1033
992
|
const random = chargeStream;
|
|
1034
993
|
random.reset(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_CHARGE_STREAM_ID));
|
|
1035
|
-
|
|
1036
|
-
let darkRealized;
|
|
994
|
+
let photoRealized = samplePoisson(photoMean, random), darkRealized;
|
|
1037
995
|
if (darkMean > 0 && darkMean < 30) {
|
|
1038
996
|
// samplePoisson's branch for 0 < mean < 30 (Knuth's product of uniforms) with its threshold Math.exp(−mean) kept while the
|
|
1039
997
|
// mean repeats: the same draws from the same stream. (createPoissonSampler would also do this, but it brings the
|
|
@@ -1052,11 +1010,17 @@ function flatSensorSite(f, photoE, darkE, total, x, y, index, seed) {
|
|
|
1052
1010
|
}
|
|
1053
1011
|
else
|
|
1054
1012
|
darkRealized = samplePoisson(darkMean, random);
|
|
1013
|
+
const b = f.b;
|
|
1014
|
+
if (b) {
|
|
1015
|
+
b.split(index, photoMean, darkMean, photoRealized, darkRealized);
|
|
1016
|
+
photoRealized = b.photo;
|
|
1017
|
+
darkRealized = b.dark;
|
|
1018
|
+
}
|
|
1055
1019
|
const realized = photoRealized + darkRealized + 0;
|
|
1056
1020
|
if (!Number.isFinite(realized) || realized < 0)
|
|
1057
1021
|
return false;
|
|
1058
1022
|
const readRandom = readNoiseStream;
|
|
1059
|
-
readRandom.reset(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_READ_NOISE_STREAM_ID));
|
|
1023
|
+
readRandom.reset(deriveNativeSiteStreamSeedUint32(seed, index, b ? b.read : NATIVE_SITE_READ_NOISE_STREAM_ID));
|
|
1060
1024
|
const capacity = f.capacity;
|
|
1061
1025
|
const afterPhysical = Math.min(realized, capacity), physicalSaturation = afterPhysical < realized;
|
|
1062
1026
|
const rmsAll = f.rms;
|
|
@@ -1088,6 +1052,8 @@ function flatSensorSite(f, photoE, darkE, total, x, y, index, seed) {
|
|
|
1088
1052
|
out.digitalSaturationCode = digitalSaturationCode;
|
|
1089
1053
|
out.flags = Number(physicalSaturation) | Number(preAdcSaturation) << 1 | Number(quantized >= digitalSaturationCode) << 2;
|
|
1090
1054
|
out.whiteCode = f.whiteCode;
|
|
1055
|
+
out.photoRealized = photoRealized;
|
|
1056
|
+
out.darkRealized = darkRealized;
|
|
1091
1057
|
return true;
|
|
1092
1058
|
}
|
|
1093
1059
|
/** The Kahan (compensated) total that composeSensorAccumulatedCharge computes, for photo and dark charge only. */
|
|
@@ -1100,28 +1066,32 @@ function chargeTotal(photoE, darkE) {
|
|
|
1100
1066
|
}
|
|
1101
1067
|
return sum;
|
|
1102
1068
|
}
|
|
1069
|
+
/**
|
|
1070
|
+
* True when the flat stage `f` reproduces the fully computed site `result` bit for bit: its dark and total charge, both charge
|
|
1071
|
+
* realizations, its codes and its flags.
|
|
1072
|
+
*/
|
|
1073
|
+
function reproducesSite(f, parsed, result, photo, x, y, index, seed) {
|
|
1074
|
+
const darkE = f.darkRate * photo.localExposureDurationSeconds, total = chargeTotal(photo.expectedGeneratedElectronCount, darkE);
|
|
1075
|
+
const r = result.readout.value, realization = result.realization.value, out = f.out;
|
|
1076
|
+
return flatSensorSite(f, photo.expectedGeneratedElectronCount, darkE, total, x, y, index, seed) && Object.is(darkE, parsed.charge.darkCharge.expectedDarkElectronCount) &&
|
|
1077
|
+
Object.is(total, result.accumulatedCharge.value.totalExpectedStoredElectronCount) &&
|
|
1078
|
+
Object.is(out.photoRealized, realization.photoRealizedElectronCount) && Object.is(out.darkRealized, realization.darkRealizedElectronCount) &&
|
|
1079
|
+
realization.additionalComponents.length === 0 && out.rawCode === r.rawCode && out.blackLevelCode === r.blackLevelCode && out.digitalSaturationCode === r.digitalSaturationCode &&
|
|
1080
|
+
out.flags === (Number(r.physicalScalarSaturationApplied) | Number(r.preAdcSaturationApplied) << 1 | Number(r.digitalSaturationApplied) << 2);
|
|
1081
|
+
}
|
|
1103
1082
|
/**
|
|
1104
1083
|
* #394: a sensor template from one fully parsed and computed site (`calculateNativeRawSite`), as the lean sensor stage builds
|
|
1105
1084
|
* its templates; undefined unless the lean arithmetic reproduces that site's dark and total charge, both charge realizations,
|
|
1106
1085
|
* its codes and its flags bit for bit. Its `compute` then gives `calculateNativeRawSite`'s outputs for any site of the same
|
|
1107
1086
|
* readout inputs (same CFA channel, dark current, capacity, readout regime and fixed-pattern profile).
|
|
1108
1087
|
*/
|
|
1109
|
-
export function createVerifiedNativeSiteReadout(parsed, result, whiteCode, photo, x, y, index, seed) {
|
|
1088
|
+
export function createVerifiedNativeSiteReadout(parsed, result, whiteCode, photo, x, y, index, seed, b) {
|
|
1110
1089
|
try {
|
|
1111
1090
|
const readout = resolveSensorReadoutRegime({ profile: parsed.readoutProfile, regimeId: parsed.regimeId });
|
|
1112
1091
|
const t = { darkRate: parsed.charge.darkCharge.darkCurrentElectronsPerSecond,
|
|
1113
1092
|
capacity: result.capacity.value.physicalChargeCapacityElectrons, readout, pattern: parsed.fixedPatternNoise, whiteCode };
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
if (!out || !Object.is(darkE, parsed.charge.darkCharge.expectedDarkElectronCount) || !Object.is(total, result.accumulatedCharge.value.totalExpectedStoredElectronCount) ||
|
|
1117
|
-
!Object.is(out.photoRealized, realization.photoRealizedElectronCount) || !Object.is(out.darkRealized, realization.darkRealizedElectronCount) ||
|
|
1118
|
-
realization.additionalComponents.length !== 0 || out.rawCode !== r.rawCode || out.blackLevelCode !== r.blackLevelCode || out.digitalSaturationCode !== r.digitalSaturationCode ||
|
|
1119
|
-
out.flags !== (Number(r.physicalScalarSaturationApplied) | Number(r.preAdcSaturationApplied) << 1 | Number(r.digitalSaturationApplied) << 2))
|
|
1120
|
-
return undefined;
|
|
1121
|
-
// #449: the flat stage must reproduce the verification site too.
|
|
1122
|
-
const flat = flatSensorStage(t), fo = flat.out;
|
|
1123
|
-
if (!flatSensorSite(flat, photo.expectedGeneratedElectronCount, darkE, total, x, y, index, seed) || fo.rawCode !== out.rawCode || fo.blackLevelCode !== out.blackLevelCode ||
|
|
1124
|
-
fo.digitalSaturationCode !== out.digitalSaturationCode || fo.flags !== out.flags || fo.whiteCode !== out.whiteCode)
|
|
1093
|
+
const flat = flatSensorStage(t, b), fo = flat.out;
|
|
1094
|
+
if (!reproducesSite(flat, parsed, result, photo, x, y, index, seed))
|
|
1125
1095
|
return undefined;
|
|
1126
1096
|
return {
|
|
1127
1097
|
compute(photoE, duration, sx, sy, siteIndex, siteSeed) {
|
|
@@ -1193,20 +1163,21 @@ export function createNativeEnvironmentSensorLean(frame, producerFrame, nativeWi
|
|
|
1193
1163
|
claim(site) {
|
|
1194
1164
|
return typeof site === "object" && site !== null && handles.has(site) ? site : undefined;
|
|
1195
1165
|
},
|
|
1196
|
-
compute(handle, index, seed, timing) {
|
|
1197
|
-
|
|
1198
|
-
|
|
1166
|
+
compute(handle, index, seed, timing, b) {
|
|
1167
|
+
// The template's flat stage (made in observe(), with the same build-up stage); the caller copies the outputs at once.
|
|
1168
|
+
const h = handles.get(handle), f = h.template.flat;
|
|
1169
|
+
if (flatSensorSite(f, h.photo.expectedGeneratedElectronCount, h.darkE, h.total, h.x, h.y, index, seed)) {
|
|
1199
1170
|
nativeEnvironmentLeanStatistics.sensorSites++;
|
|
1200
|
-
|
|
1201
|
-
|
|
1171
|
+
return f.out;
|
|
1172
|
+
}
|
|
1202
1173
|
// A compute-phase check failed: the full path computes (and throws) exactly as it would have.
|
|
1203
1174
|
const parsed = parseNativeRawProducerSite(composeNativeEnvironmentSensorSite(h.site, h.photo), index, producerFrame, timing);
|
|
1204
|
-
const r = calculateNativeRawSite(parsed, seed, index).readout.value;
|
|
1175
|
+
const r = calculateNativeRawSite(parsed, seed, index, b).readout.value;
|
|
1205
1176
|
return { rawCode: r.rawCode, blackLevelCode: r.blackLevelCode, digitalSaturationCode: r.digitalSaturationCode,
|
|
1206
1177
|
flags: Number(r.physicalScalarSaturationApplied) | Number(r.preAdcSaturationApplied) << 1 | Number(r.digitalSaturationApplied) << 2,
|
|
1207
1178
|
whiteCode: calculateSiteEffectiveSaturationCode(r, parsed.readoutProfile, parsed.regimeId) };
|
|
1208
1179
|
},
|
|
1209
|
-
observe(index, parsed, result, whiteCode, seed) {
|
|
1180
|
+
observe(index, parsed, result, whiteCode, seed, b) {
|
|
1210
1181
|
const entry = pending.get(index);
|
|
1211
1182
|
pending.delete(index);
|
|
1212
1183
|
if (!entry || templates.length >= MAXIMUM_TEMPLATES)
|
|
@@ -1219,18 +1190,11 @@ export function createNativeEnvironmentSensorLean(frame, producerFrame, nativeWi
|
|
|
1219
1190
|
const t = { site, photo, phase, x, y, verified: false, darkRate: parsed.charge.darkCharge.darkCurrentElectronsPerSecond,
|
|
1220
1191
|
capacity: result.capacity.value.physicalChargeCapacityElectrons, readout, pattern: parsed.fixedPatternNoise, whiteCode };
|
|
1221
1192
|
templates.push(t);
|
|
1222
|
-
// Self-check: the template matches itself and
|
|
1193
|
+
// Self-check: the template matches itself and its flat stage reproduces its full computation bit for bit.
|
|
1223
1194
|
if (!sensorMatches(site, photo, x, y, t, new WeakMap()))
|
|
1224
1195
|
return;
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
const out = leanSensorSite(t, { photo, darkE, total, x, y }, index, seed);
|
|
1228
|
-
if (!out || !Object.is(darkE, parsed.charge.darkCharge.expectedDarkElectronCount) ||
|
|
1229
|
-
!Object.is(total, result.accumulatedCharge.value.totalExpectedStoredElectronCount) ||
|
|
1230
|
-
!Object.is(out.photoRealized, realization.photoRealizedElectronCount) || !Object.is(out.darkRealized, realization.darkRealizedElectronCount) ||
|
|
1231
|
-
realization.additionalComponents.length !== 0 || out.rawCode !== r.rawCode || out.blackLevelCode !== r.blackLevelCode ||
|
|
1232
|
-
out.digitalSaturationCode !== r.digitalSaturationCode || out.flags !== (Number(r.physicalScalarSaturationApplied) |
|
|
1233
|
-
Number(r.preAdcSaturationApplied) << 1 | Number(r.digitalSaturationApplied) << 2))
|
|
1196
|
+
t.flat = flatSensorStage(t, b);
|
|
1197
|
+
if (!reproducesSite(t.flat, parsed, result, photo, x, y, index, seed))
|
|
1234
1198
|
return;
|
|
1235
1199
|
t.verified = true;
|
|
1236
1200
|
}
|