@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/docs/USAGE.md
CHANGED
|
@@ -7146,7 +7146,9 @@ console.log(png.length, same);
|
|
|
7146
7146
|
See [Print planning](https://github.com/Photivra/photivra/blob/main/docs/PRINT_PLANNING.md) for a complete, strict packed example and the exact-ratio integer policy.
|
|
7147
7147
|
|
|
7148
7148
|
- `calculatePrintPlan()` says whether the native sampling is enough, and gives a compatible minimum file raster.
|
|
7149
|
+
- `fit: { kind: "contain", paperOrientation }` (2.22.0) fits the whole image inside paper of another shape and reports the borders ([Contain fit](https://github.com/Photivra/photivra/blob/main/docs/PRINT_PLANNING.md#integer-policy-fit-and-sufficiency)).
|
|
7149
7150
|
- `calculatePrintSizeLimit()` distinguishes finite, unbounded and unavailable sampling limits.
|
|
7151
|
+
- `calculatePrintSizeLimitWithBlurAndNoise()` (2.20.0) gives the largest print at a viewing distance that still looks sharp and clean, counting blur and noise as well as pixels, and names the limiting factor ([Print size limit with blur and noise](https://github.com/Photivra/photivra/blob/main/docs/PRINT_PLANNING.md#print-size-limit-with-blur-and-noise)). An approximation.
|
|
7150
7152
|
- Print quality and delivery are not assessed.
|
|
7151
7153
|
|
|
7152
7154
|
The [regional Print detail diagnostic](https://github.com/Photivra/photivra/blob/main/docs/PRINT_DETAIL_ASSESSMENT.md) measures one declared, coherent sinusoidal fundamental in a selected linear-luminance ROI, and records the exact pipeline stage it measured. Its [validation protocol](https://github.com/Photivra/photivra/blob/main/docs/PRINT_DETAIL_VALIDATION.md) and [capability ledger](https://github.com/Photivra/photivra/blob/main/docs/validation/print-detail-capabilities.json) separate mathematical conformance from the still-blocked qualification of acquired images, renderers and perceived quality.
|
|
@@ -7511,17 +7513,41 @@ console.log(calculateGeometricFocusBreathing({
|
|
|
7511
7513
|
}).value.deltaDegrees);
|
|
7512
7514
|
```
|
|
7513
7515
|
|
|
7516
|
+
## Sensor profiles (#554)
|
|
7517
|
+
|
|
7518
|
+
`SENSOR_PROFILES` lists eight representative sensors (full frame, APS-C, medium format and a large phone sensor; BSI,
|
|
7519
|
+
stacked and front-illuminated; 2010s and 2020s) whose values come from published specifications, values derived from them
|
|
7520
|
+
or labelled design values, with no maker or product named. `simulateCapture({ sensor: { profile: record } })` simulates one
|
|
7521
|
+
(the record is checked strictly; `resolveSensorProfile(id)` finds one by id):
|
|
7522
|
+
it replaces the body's ISO range, readout regimes, readout time and fixed-pattern noise, and the #555 characteristics
|
|
7523
|
+
override it one at a time. `resolveSensorProfile(id)` returns one profile; `SENSOR_PROFILE_ISO_ANCHOR` is the ISO anchor its base ISO is re-rated from. Values, sources and the ISO
|
|
7524
|
+
rating: [SENSOR_PROFILES.md](https://github.com/Photivra/photivra/blob/main/docs/SENSOR_PROFILES.md).
|
|
7525
|
+
|
|
7526
|
+
```ts
|
|
7527
|
+
import { SENSOR_PROFILES, simulateCapture } from "@photivra/engine";
|
|
7528
|
+
|
|
7529
|
+
for (const p of SENSOR_PROFILES) {
|
|
7530
|
+
// fast: false (full quality, as the calculator asks) computes the mid-gray noise figure.
|
|
7531
|
+
const r = await simulateCapture({ scene: { preset: "uniform" }, fast: false, sensor: { profile: p }, exposure: { mode: "manual", iso: 3200, shutterSeconds: 1 / 4000 } });
|
|
7532
|
+
console.log(p.name, r.sensor.dynamicRangeStops.toFixed(1), r.sensor.midGrey?.snr.toFixed(1));
|
|
7533
|
+
}
|
|
7534
|
+
```
|
|
7535
|
+
|
|
7514
7536
|
## Generic lens catalogue (#549)
|
|
7515
7537
|
|
|
7516
7538
|
`GENERIC_LENS_CATALOGUE` lists the engine's 30 generic primes (17 professional, 13 consumer, "PV 50 1.2 Pro", "PV 50 1.8"; Photivra's
|
|
7517
7539
|
own designs, not real products). `resolveGenericLens` resolves one for a sensor, an f-number and a focus distance: its focal length
|
|
7518
7540
|
(scaled by the crop factor), aperture range, close focus, projection and its character, Seidel aberrations, axial and lateral
|
|
7519
|
-
colour, distortion and vignetting profiles, flare, breathing
|
|
7541
|
+
colour, distortion and vignetting profiles, flare, breathing, focus scatter and its bokeh (`character.bokeh`, #567: the options of
|
|
7542
|
+
`lens.defocus` and `calculateDefocusBokehKernel`), each a labelled approximation.
|
|
7520
7543
|
`simulateGenericLensFocusError` draws a seeded autofocus error; `createGenericLensCorrectionProfile` builds the lens's generic
|
|
7521
7544
|
correction profile for `resolveLensCorrectionPlan`, which `simulateCapture` applies to its developed image with
|
|
7522
|
-
`output.lensCorrections` (#570; never to the RAW or the DNG)
|
|
7545
|
+
`output.lensCorrections` (#570; never to the RAW or the DNG), and which a DNG can carry for RAW editors as optional DNG opcodes with
|
|
7546
|
+
`output.dng.lensCorrectionOpcodes` (#587; the RAW codes unchanged). `simulateCapture` renders a catalogue lens with `lens.catalogue`
|
|
7523
7547
|
([quick start](QUICK_START.md#generic-lens-catalogue-549)); `fast: true` (or `lens: { catalogue: { kind, fast: true } }`) takes its
|
|
7524
|
-
fast method (#568), a labeled approximation that blends the PSF over a field grid, for interactive captures.
|
|
7548
|
+
fast method (#568), a labeled approximation that blends the PSF over a field grid, for interactive captures.
|
|
7549
|
+
`calculateLateralColorFringe({ lensId, activeImagingArea, pixelPitchMicrometers, fieldPosition? })` gives the lens's lateral color as a
|
|
7550
|
+
fringe width: the blue-minus-red image displacement in µm and pixels at a field position (default the corner; #580). Equations, tables and the
|
|
7525
7551
|
chart measurements: [LENS_CATALOGUE.md](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md).
|
|
7526
7552
|
|
|
7527
7553
|
```ts
|
|
@@ -7787,6 +7813,13 @@ including the whole-image stages `output.diffractionCorrection` and `output.shar
|
|
|
7787
7813
|
bytes as a whole-frame `simulateCapture()` and per-row progress (`diffraction-correction`, `sharpening`). See
|
|
7788
7814
|
[Quick start, Developing a capture rendered in parts](QUICK_START.md#developing-a-capture-rendered-in-parts-571).
|
|
7789
7815
|
|
|
7816
|
+
`simulateExposureBuildUp(options, { fractions })` (#534) renders fractions of one exposure as one accumulating process. Each
|
|
7817
|
+
fraction's expected signal is its own capture at the elapsed time, so camera rotation, a moving subject and mixed lighting are
|
|
7818
|
+
included. Its photo and dark counts are binomial splits of the next fraction's, so they build up in place. Each fraction is
|
|
7819
|
+
clipped at the full well, read out once with its own read noise and developed as its own photo. The fraction at 1 is
|
|
7820
|
+
`simulateCapture(options)` byte for byte. Only the sRGB sample formats are accepted (on screen). See
|
|
7821
|
+
[Quick start, Exposure build-up](QUICK_START.md#exposure-build-up-534).
|
|
7822
|
+
|
|
7790
7823
|
### Color charts (2.14)
|
|
7791
7824
|
|
|
7792
7825
|
The engine's own [color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md) are reflectance spectra lit by the capture's illuminant. Draw the
|
|
@@ -7807,6 +7840,33 @@ const sky = getColorPatchSet().patches.find((p) => p.patchId === "sky-blue");
|
|
|
7807
7840
|
console.log(tungsten.scene.parameters.patchSet, sky?.code, sky?.name, sky?.reference.cielabD65); // "PV-M1", "Midday Sky"
|
|
7808
7841
|
```
|
|
7809
7842
|
|
|
7843
|
+
### A photo as a print (2.21)
|
|
7844
|
+
|
|
7845
|
+
An [image-plane scene](https://github.com/Photivra/photivra/blob/main/docs/IMAGE_PLANE.md) (#581) puts an sRGB (8- or 16-bit) or linear image in front of the camera as a flat matte
|
|
7846
|
+
print. Each pixel becomes a smooth reflectance spectrum in [0, 1] that gives back its sRGB under CIE D65. Under other lights the print's colors are an approximation.
|
|
7847
|
+
`chartSize` sets the print's size (its height follows the image). Without it the print is fitted inside the frame. `prepareDisplayView` renders it on WebGPU in the Speed path.
|
|
7848
|
+
`prepareImagePlaneScenes()` loads the scene's module first, so bundles that never print a photo do not carry it.
|
|
7849
|
+
|
|
7850
|
+
```ts
|
|
7851
|
+
import { prepareImagePlaneScenes, simulateCapture } from "@photivra/engine";
|
|
7852
|
+
|
|
7853
|
+
await prepareImagePlaneScenes(); // loads the scene's module (a lazy chunk) once
|
|
7854
|
+
const width = 2, height = 1, data = Uint8Array.of(255, 255, 255, 30, 60, 200);
|
|
7855
|
+
const print = await simulateCapture({
|
|
7856
|
+
scene: { kind: "image-plane", sceneId: "snapshot", image: { width, height, channels: 3, encoding: "srgb-8", data }, paper: { whiteReflectance: 0.9 } },
|
|
7857
|
+
chartSize: { widthM: 0.1 },
|
|
7858
|
+
subjectDistanceM: 0.4,
|
|
7859
|
+
output: { width: 32, height: 16 }
|
|
7860
|
+
});
|
|
7861
|
+
console.log(print.scene.parameters.encoding, print.assumptions.some((a) => a.id === "scene.imagePlane")); // "srgb-8", true
|
|
7862
|
+
```
|
|
7863
|
+
|
|
7864
|
+
**Representative light sources (2.22).** The engine's own [representative light sources](https://github.com/Photivra/photivra/blob/main/docs/LIGHT_SOURCES.md) are four relative spectra
|
|
7865
|
+
designed by Photivra: a tri-phosphor fluorescent tube and white LEDs of low and high color rendering at 4000 K and a warm one at
|
|
7866
|
+
2700 K, each "representative, not a named product". `createRepresentativeLightSourceIlluminant({ sourceId })` returns an
|
|
7867
|
+
`illuminant` (`{ kind: "spectrum", spectrum, label }`); under the two 4000 K LEDs the color patches differ by up to ΔE00 5.9 while
|
|
7868
|
+
the neutrals stay neutral. Use `sensor.spectralStepNanometers: 5`. The page has a runnable example.
|
|
7869
|
+
|
|
7810
7870
|
In `aperture-priority` mode, the shutter comes from the meter (CIPA DC-004 relations S = 10/H and H = 0.65·L·t/N²) through
|
|
7811
7871
|
`resolveAperturePriorityExposureMode()`.
|
|
7812
7872
|
`exposure.standardExposureDeviationStops` is the exposure used minus the standard exposure for the metered luminance L, in stops,
|
|
@@ -7863,6 +7923,28 @@ The chart presets are also available on their own:
|
|
|
7863
7923
|
- `createPlanarSceneRadianceProvider()` turns a chart into an `evaluateRadiance` callback for the native
|
|
7864
7924
|
environment RAW contract.
|
|
7865
7925
|
|
|
7926
|
+
### Mixed lighting (2.22, #550)
|
|
7927
|
+
|
|
7928
|
+
`illumination` lights the chart with two or more sources blended across it (a linear gradient, a radial falloff or regions),
|
|
7929
|
+
for example window daylight on one side and a lamp on the other. The frame center's light sets the grey-card meter and its
|
|
7930
|
+
strongest source the `"scene-illuminant"` white balance; both are reported in `assumptions`. See
|
|
7931
|
+
[Mixed lighting](https://github.com/Photivra/photivra/blob/main/docs/MIXED_ILLUMINATION.md) for the model and every field.
|
|
7932
|
+
|
|
7933
|
+
```ts
|
|
7934
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7935
|
+
|
|
7936
|
+
const lampPool = await simulateCapture({
|
|
7937
|
+
scene: { preset: "step-wedge" },
|
|
7938
|
+
exposure: { mode: "aperture-priority", metering: "grey-card" },
|
|
7939
|
+
illumination: {
|
|
7940
|
+
sources: [{ illuminant: "cie-a", illuminanceLux: 500 }, { illuminant: { kind: "planckian", temperatureKelvin: 6500 }, illuminanceLux: 20000 }],
|
|
7941
|
+
blend: { kind: "radial", radius: 0.8, stops: [0.25, 1] }
|
|
7942
|
+
},
|
|
7943
|
+
output: { width: 32, height: 16 }
|
|
7944
|
+
});
|
|
7945
|
+
console.log(lampPool.assumptions.find((a) => a.id === "output.whiteBalanceSource")?.value); // { source: 0, illuminant: "cie-a" }
|
|
7946
|
+
```
|
|
7947
|
+
|
|
7866
7948
|
### Display view of a capture
|
|
7867
7949
|
|
|
7868
7950
|
`simulateDisplayView(options, view)` takes a capture request and a display size and returns the real
|
|
@@ -8048,6 +8130,18 @@ const rolling = await simulateCapture({ scene: { preset: "checkerboard" }, motio
|
|
|
8048
8130
|
console.log(rolling.request.siteInput(7 * 8).charge.completenessProfile.startOffsetSecondsFromOpeningReference);
|
|
8049
8131
|
```
|
|
8050
8132
|
|
|
8133
|
+
`motion.subject` (2.22.0) moves the subject instead: the whole scene, or one `region` of it over a still background,
|
|
8134
|
+
at `velocityMPerSec` on the scene plane, optionally rotating at `angularVelocityRadPerSec` about the optical axis. It
|
|
8135
|
+
uses the same temporal midpoints, so a rolling shutter skews the moving subject. The display view renders sideways
|
|
8136
|
+
motion under a global shutter and refuses the rest. See [Subject motion](QUICK_START.md#subject-motion).
|
|
8137
|
+
|
|
8138
|
+
```ts
|
|
8139
|
+
import { simulateCapture } from "@photivra/engine";
|
|
8140
|
+
|
|
8141
|
+
const moving = await simulateCapture({ scene: { preset: "slanted-edge" }, motion: { subject: { velocityMPerSec: { x: 0.5 } } }, output: { width: 8, height: 8 } });
|
|
8142
|
+
console.log(moving.assumptions.find((a) => a.id === "motion.subject")?.value);
|
|
8143
|
+
```
|
|
8144
|
+
|
|
8051
8145
|
## Equivalent f-number, sensor area and blur as a share of the frame
|
|
8052
8146
|
|
|
8053
8147
|
`calculateSensorEquivalence()` says what a lens on one sensor corresponds to on a 36 × 24 mm frame (or on a `reference` you pass). Call it once per sensor to fill a lens table:
|
|
@@ -8085,3 +8179,63 @@ console.log(share.print?.blurOnPrintMm); // 0.25 mm on the print
|
|
|
8085
8179
|
- Equal shares of the frame diagonal look the same when images are shown at the same size. `calculateBlurShareOfFrame()` does not judge whether a blur is visible.
|
|
8086
8180
|
|
|
8087
8181
|
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#sensor-equivalence-f-number-area-and-blur-share).
|
|
8182
|
+
|
|
8183
|
+
## Aperture diameter and cropping to match a longer lens
|
|
8184
|
+
|
|
8185
|
+
`calculateApertureDiameter()` gives the entrance-pupil diameter in millimetres, focal length ÷ f-number (exact by the definition of the f-number, ideal lens focused at infinity). `calculateCropToMatchFocalLength()` gives the centred crop that makes an image from a shorter lens match a longer lens's framing from the same position, and the pixels it leaves:
|
|
8186
|
+
|
|
8187
|
+
```ts
|
|
8188
|
+
import { calculateApertureDiameter, calculateCropToMatchFocalLength } from "@photivra/engine";
|
|
8189
|
+
|
|
8190
|
+
console.log(calculateApertureDiameter({ focalLengthMm: 50, fNumber: 2 }).value.entrancePupilDiameterMm); // 25
|
|
8191
|
+
console.log(calculateApertureDiameter({ focalLengthMm: 50, fNumber: 4 }).value.entrancePupilDiameterMm); // 12.5
|
|
8192
|
+
|
|
8193
|
+
const crop = calculateCropToMatchFocalLength({
|
|
8194
|
+
focalLengthMm: 50,
|
|
8195
|
+
targetFocalLengthMm: 100,
|
|
8196
|
+
pixelWidth: 6000,
|
|
8197
|
+
pixelHeight: 4000
|
|
8198
|
+
}).value;
|
|
8199
|
+
|
|
8200
|
+
console.log(crop.cropFactor); // 2
|
|
8201
|
+
console.log(crop.pixelWidth, crop.pixelHeight); // 3000 2000
|
|
8202
|
+
console.log(crop.megapixels, crop.sourceMegapixels); // 6 24
|
|
8203
|
+
```
|
|
8204
|
+
|
|
8205
|
+
- The aperture diameter is the entrance pupil (the aperture stop seen from the front of the lens), not the diaphragm's opening. Close focus (working f-number `N·(1 + m/P)`, pupil magnification `P`) is not modelled.
|
|
8206
|
+
- The crop factor `target ÷ focal length` is exact for ideal rectilinear lenses focused at infinity; at close focus the matching factor is larger, `F·(d − f)/(f·(d − F))` at object distance `d` (thin lens), which is not applied. The crop is `calculateCenteredCrop()`'s (aspect ratio kept to whole pixels), and `targetFocalLengthMm` must be at least `focalLengthMm`.
|
|
8207
|
+
|
|
8208
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#aperture-entrance-pupil-diameter).
|
|
8209
|
+
|
|
8210
|
+
## Which limits detail: the lens or the sensor
|
|
8211
|
+
|
|
8212
|
+
`calculateDetailLimit()` (model `detail-limit` 1.0.0, #575) answers "would a sharper lens or a denser sensor help?" for given settings. It returns the optics' resolution (MTF50 in cycles/mm, from diffraction, the lens's aberrations and any defocus), the sampling's (the Nyquist frequency 1/(2p) and, with a Bayer filter, the red and blue sites' 1/(4p)), the system MTF50, and which one limits:
|
|
8213
|
+
|
|
8214
|
+
```ts
|
|
8215
|
+
import { calculateDetailLimit } from "@photivra/engine";
|
|
8216
|
+
|
|
8217
|
+
const sensor = { widthMm: 36, heightMm: 24, pixelWidth: 6000 }; // 6 µm pixels
|
|
8218
|
+
|
|
8219
|
+
const stoppedDown = calculateDetailLimit({ sensor, lens: { kind: "ideal", focalLengthMm: 50 }, fNumber: 22 }).value;
|
|
8220
|
+
console.log(stoppedDown.limitedBy, stoppedDown.limitingFactor); // "lens" "diffraction"
|
|
8221
|
+
console.log(stoppedDown.optics.mtf50CyclesPerMm); // ≈ 33.4, against a Nyquist frequency of ≈ 83.3
|
|
8222
|
+
console.log(stoppedDown.diffractionLimitedFNumber); // ≈ 8.8: from here on diffraction limits on this sensor
|
|
8223
|
+
|
|
8224
|
+
const wideOpen = calculateDetailLimit({ sensor, lens: { kind: "catalogue", lensId: "pv-50-1.2-pro" }, fNumber: 4 }).value;
|
|
8225
|
+
console.log(wideOpen.limitedBy, wideOpen.limitingFactor); // "sensor" "pixel-pitch"
|
|
8226
|
+
|
|
8227
|
+
const outOfFocus = calculateDetailLimit({
|
|
8228
|
+
sensor,
|
|
8229
|
+
lens: { kind: "ideal", focalLengthMm: 50 },
|
|
8230
|
+
fNumber: 2,
|
|
8231
|
+
focus: { focusDistanceM: 3, subjectDistanceM: 2.5 }
|
|
8232
|
+
}).value;
|
|
8233
|
+
console.log(outOfFocus.limitingFactor); // "defocus"
|
|
8234
|
+
```
|
|
8235
|
+
|
|
8236
|
+
- The sensor limits when the optics still pass 50 % contrast at the Nyquist frequency (`opticsToNyquistRatio ≥ 1`): a denser sensor would record finer detail. Otherwise the lens limits, through `"diffraction"` when its RMS wavefront error is within λ/14 (the Maréchal criterion), else `"aberrations"` or `"defocus"`, whichever is larger. This rule is an approximation; the MTF values are the exact scalar OTF of the stated wavefront at one wavelength (550 nm unless `wavelengthNm` is given).
|
|
8237
|
+
- `lens.catalogue` lenses use their generic Seidel aberrations at `fieldPosition` (0 the center, 1 the corner), so corners are softer; `lens.ideal` has none.
|
|
8238
|
+
- Tangential and sagittal MTF50 are both reported; the decision uses the lower one.
|
|
8239
|
+
- Demosaicing, sharpening, noise and motion are not included.
|
|
8240
|
+
|
|
8241
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#which-limits-detail-the-lens-or-the-sensor).
|
|
@@ -82,5 +82,11 @@ export interface SerializeNativeRawDngInput {
|
|
|
82
82
|
capturedAtUtc?: string;
|
|
83
83
|
/** Optional (2.4) caller text: description, software, copyright, comment (see the module notes). If omitted, no text is added. */
|
|
84
84
|
textMetadata?: OutputTextMetadata;
|
|
85
|
+
/**
|
|
86
|
+
* Optional (#587) OpcodeList3 (tag 51022) bytes, for example `createDngLensCorrectionOpcodes(...).opcodeList3`: a big-endian DNG
|
|
87
|
+
* opcode list (DNG 1.3.0.0+, "Opcode List Processing") that readers apply to the demosaiced stage-3 image. Only its framing is
|
|
88
|
+
* checked (count, per-opcode headers and parameter sizes filling the bytes exactly). The RAW codes are unchanged. If omitted, no tag.
|
|
89
|
+
*/
|
|
90
|
+
opcodeList3?: Uint8Array;
|
|
85
91
|
}
|
|
86
92
|
```
|
package/docs/api/core-version.md
CHANGED
|
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
|
|
|
11
11
|
own versions. Release checks confirm it matches package.json and the packed package.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
ENGINE_API_VERSION = "2.
|
|
14
|
+
ENGINE_API_VERSION = "2.21.0" as const
|
|
15
15
|
```
|
|
@@ -4,11 +4,11 @@ The package and root API versions are recorded once in [Navigation](../API_REFER
|
|
|
4
4
|
|
|
5
5
|
## GENERIC_LENS_CATALOGUE
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The 33 lenses: 17 professional, 13 consumer and (#554) 3 phone lenses, each set by focal length.
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
GENERIC_LENS_CATALOGUE: readonly GenericLensCatalogueEntry[] = Object.freeze([...PRO.map((row) => entry("professional", row)),
|
|
11
|
-
...CONSUMER.map((row) => entry("consumer", row))])
|
|
11
|
+
...CONSUMER.map((row) => entry("consumer", row)), ...PHONE.map((row) => entry("phone", row))])
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## GENERIC_LENS_IDS
|
|
@@ -30,7 +30,12 @@ GENERIC_LENS_SET_BASELINES: Readonly<Record<GenericLensSet, Readonly<GenericLens
|
|
|
30
30
|
breathingFraction: -0.03, focusScatterDepthOfFocusFraction: 0.1, closeFocusFactor: 0.15 }),
|
|
31
31
|
consumer: Object.freeze({ sphericalWaves: 0.5, comaWaves: 1.0, astigmatismWaves: 0.8, fieldCurvatureWaves: 0.45, axialColorWaves: 0.4,
|
|
32
32
|
lateralColorFraction: 0.0003, distortionMagnitude: 0.03, naturalVignettingStops: 0.7, opticalVignettingStops: 1.1, veilingGlareFraction: 0.02, ghostFraction: 0.008,
|
|
33
|
-
breathingFraction: -0.08, focusScatterDepthOfFocusFraction: 0.3, closeFocusFactor: 0.5 })
|
|
33
|
+
breathingFraction: -0.08, focusScatterDepthOfFocusFraction: 0.3, closeFocusFactor: 0.5 }),
|
|
34
|
+
// #554: design values (docs/LENS_CATALOGUE.md, "Phone lenses"): the consumer baseline with the stronger fall-off, flare and distortion
|
|
35
|
+
// (corrected in software on phones) of small fixed-aperture phone lenses, and no optical-vignetting change (the aperture is fixed).
|
|
36
|
+
phone: Object.freeze({ sphericalWaves: 0.5, comaWaves: 1.0, astigmatismWaves: 0.8, fieldCurvatureWaves: 0.5, axialColorWaves: 0.3,
|
|
37
|
+
lateralColorFraction: 0.0004, distortionMagnitude: 0.03, naturalVignettingStops: 1.2, opticalVignettingStops: 0.5, veilingGlareFraction: 0.03, ghostFraction: 0.01,
|
|
38
|
+
breathingFraction: -0.05, focusScatterDepthOfFocusFraction: 0.3, closeFocusFactor: 0.5 })
|
|
34
39
|
})
|
|
35
40
|
```
|
|
36
41
|
|
|
@@ -69,7 +74,7 @@ export interface GenericLensCatalogueEntry {
|
|
|
69
74
|
note: string;
|
|
70
75
|
/**
|
|
71
76
|
* A short plain-English summary of the lens's character (at most 40 characters, US spelling), written from its adjustments and
|
|
72
|
-
* `note`, for lists and pickers. It describes
|
|
77
|
+
* `note`, for lists and pickers. It describes the lens's sharpness, color, distortion and falloff, not its bokeh (the same rule for every lens of a set).
|
|
73
78
|
*/
|
|
74
79
|
summary: string;
|
|
75
80
|
}
|
|
@@ -102,6 +107,11 @@ export interface GenericLensCharacter {
|
|
|
102
107
|
breathing: { fieldOfViewChange: number; atMinimumFocus: number };
|
|
103
108
|
/** Standard deviation of the autofocus error as a fraction of the depth of focus 2·N·c. */
|
|
104
109
|
focusScatterDepthOfFocusFraction: number;
|
|
110
|
+
/**
|
|
111
|
+
* Bokeh character (#567) at this f-number, focus and sensor, as the options of `lens.defocus` and `calculateDefocusBokehKernel`
|
|
112
|
+
* (#553): blades, the resolved W040 as `sphericalAberration` (µm at this f-number) and, for fast wides and normals, `catsEye`.
|
|
113
|
+
*/
|
|
114
|
+
bokeh: DefocusBokehOptions;
|
|
105
115
|
}
|
|
106
116
|
```
|
|
107
117
|
|
|
@@ -127,7 +137,7 @@ export interface GenericLensCharacterValues {
|
|
|
127
137
|
distortionMagnitude: number;
|
|
128
138
|
/** Corner fall-off in stops that stays when stopped down (natural vignetting). */
|
|
129
139
|
naturalVignettingStops: number;
|
|
130
|
-
/** Extra corner fall-off in stops at the widest aperture (optical vignetting); gone
|
|
140
|
+
/** Extra corner fall-off in stops at the widest aperture (optical vignetting); gone four stops down (N = 4·N0). */
|
|
131
141
|
opticalVignettingStops: number;
|
|
132
142
|
/** Veiling glare: the fraction of the image's mean irradiance spread as a uniform veil. */
|
|
133
143
|
veilingGlareFraction: number;
|
|
@@ -174,10 +184,10 @@ export type GenericLensKind = "standard" | "macro" | "fisheye";
|
|
|
174
184
|
|
|
175
185
|
## GenericLensSet
|
|
176
186
|
|
|
177
|
-
|
|
187
|
+
#554: "phone", the three fixed lenses of a phone sensor profile (`sensor.profile` with `fixedLenses: "phone"`).
|
|
178
188
|
|
|
179
189
|
```ts
|
|
180
|
-
export type GenericLensSet = "professional" | "consumer";
|
|
190
|
+
export type GenericLensSet = "professional" | "consumer" | "phone";
|
|
181
191
|
```
|
|
182
192
|
|
|
183
193
|
## ResolvedGenericLens
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# equipment/sensor-profile-body.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
|
+
## SENSOR_PROFILE_ISO_ANCHOR
|
|
6
|
+
|
|
7
|
+
The engine's ISO anchor (see the module comment): white charge 60 000 e⁻ at ISO 100, 36 µm², QE 0.5.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
SENSOR_PROFILE_ISO_ANCHOR = Object.freeze({ iso: 100, fullWellElectrons: 60000, pixelAreaSquareMicrometers: 36, peakQuantumEfficiency: .5 })
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## SensorProfile
|
|
14
|
+
|
|
15
|
+
One sensor profile. Every number is per photosite unless stated; see `provenance` for each value's kind.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
export interface SensorProfile {
|
|
19
|
+
id: string;
|
|
20
|
+
/** Format, resolution, architecture and era; no maker or product name. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** A market category, not a specification: it is a label and changes nothing. */
|
|
23
|
+
marketTag: "consumer" | "professional";
|
|
24
|
+
architecture: "front-illuminated" | "back-illuminated" | "stacked";
|
|
25
|
+
sensorWidthMm: number; sensorHeightMm: number;
|
|
26
|
+
pixelsWide: number; pixelsHigh: number;
|
|
27
|
+
pixelPitchMicrometers: number;
|
|
28
|
+
/** The green channel's peak EQE (#555 `peakQuantumEfficiency`). */
|
|
29
|
+
peakQuantumEfficiency: number;
|
|
30
|
+
/** Full-well capacity, e⁻: the charge at the RAW white level at base ISO. */
|
|
31
|
+
fullWellElectrons: number;
|
|
32
|
+
/** Read noise, RMS e⁻; `highGainFromIso` null for one conversion gain throughout. */
|
|
33
|
+
readNoise: { lowGainElectrons: number; highGainElectrons: number; highGainFromIso: number | null };
|
|
34
|
+
/** Charge storage limit of the high conversion gain, e⁻ (null without one): full well × base ISO / switch ISO. */
|
|
35
|
+
highGainFullWellElectrons: number | null;
|
|
36
|
+
adcBitDepth: 12 | 14 | 16;
|
|
37
|
+
/** Re-rated base ISO (see the module comment) and the highest ISO setting. */
|
|
38
|
+
baseIso: number; maximumIso: number;
|
|
39
|
+
/** Largest analog gain relative to base ISO; above it the gain is digital. */
|
|
40
|
+
maximumAnalogGain: number;
|
|
41
|
+
/** Rolling readout, s: the top-to-bottom skew with `motion.shutter: "rolling"`. */
|
|
42
|
+
readoutSeconds: number;
|
|
43
|
+
/** Fixed-pattern noise used with `sensor.fixedPatternNoise` (EMVA 1288 Release 4.0 non-uniformities). */
|
|
44
|
+
fixedPattern: { prnu: number; dsnu: number; hotPixelFraction: number; hotPixelMultiplier: number; rowElectrons: number; columnElectrons: number };
|
|
45
|
+
/** What the profile is good for (text only). */
|
|
46
|
+
goodFor: readonly string[];
|
|
47
|
+
description: string;
|
|
48
|
+
/** "phone": the profile takes only the catalogue's phone lenses (`lens.catalogue`), which need it. */
|
|
49
|
+
fixedLenses: "phone" | null;
|
|
50
|
+
/**
|
|
51
|
+
* Each value's kind and source key (S1–S10, docs/SENSOR_PROFILES.md "Sources"); a value not listed is a design value.
|
|
52
|
+
* For example `fullWellElectrons: "published S1"` or `baseIso: "derived"`.
|
|
53
|
+
*/
|
|
54
|
+
provenance: Readonly<Partial<Record<keyof SensorProfile | "sensorSize" | "pixels" | "highGainFromIso", string>>>;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## SensorProfileValueKind
|
|
59
|
+
|
|
60
|
+
How a profile value was obtained: copied from a source, calculated from sourced values, or Photivra's design value.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
export type SensorProfileValueKind = "published" | "derived" | "design";
|
|
64
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# equipment/sensor-profiles.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
|
+
## resolveSensorProfile
|
|
6
|
+
|
|
7
|
+
The profile record with this id (for `sensor.profile`); a clear error listing the ids otherwise.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
export function resolveSensorProfile(id: unknown): SensorProfile;
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This call is synchronous; any supplied provider must follow its explicit synchronous contract.
|
|
14
|
+
|
|
15
|
+
## SENSOR_PROFILE_IDS
|
|
16
|
+
|
|
17
|
+
The profile ids, in catalogue order.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
SENSOR_PROFILE_IDS: readonly string[] = Object.freeze(SENSOR_PROFILES.map((p) => p.id))
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## SENSOR_PROFILES
|
|
24
|
+
|
|
25
|
+
The catalogue, in the issue's order. Frozen.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
SENSOR_PROFILES: readonly SensorProfile[] = Object.freeze(([
|
|
29
|
+
{ id: "full-frame-24mp-bsi-2020s", name: "Full frame 24 MP BSI, 2020s", marketTag: "consumer", architecture: "back-illuminated",
|
|
30
|
+
sensorWidthMm: 36.07, sensorHeightMm: 24.01, pixelsWide: 6072, pixelsHigh: 4042, pixelPitchMicrometers: 5.94, peakQuantumEfficiency: .8,
|
|
31
|
+
fullWellElectrons: 100000, readNoise: { lowGainElectrons: 6.4, highGainElectrons: 1.1, highGainFromIso: 500 }, highGainFullWellElectrons: 20000,
|
|
32
|
+
adcBitDepth: 14, baseIso: 100, maximumIso: 51200, maximumAnalogGain: 64, readoutSeconds: .05, fixedPattern: BSI,
|
|
33
|
+
goodFor: ["landscape", "portrait", "travel"], description: "Large pixels and a deep full well for wide dynamic range at base ISO; a moderate readout shows some rolling-shutter skew.",
|
|
34
|
+
fixedLenses: null, provenance: { ...sourced("S1", "pixels", "pixelPitchMicrometers", "peakQuantumEfficiency", "fullWellElectrons", "readNoise", "adcBitDepth"),
|
|
35
|
+
sensorSize: "derived", architecture: "published S1", highGainFromIso: "derived S1", highGainFullWellElectrons: "derived", baseIso: "derived" } },
|
|
36
|
+
{ id: "full-frame-45mp-stacked-2020s", name: "Full frame 45 MP stacked, 2020s", marketTag: "professional", architecture: "stacked",
|
|
37
|
+
sensorWidthMm: 36, sensorHeightMm: 24, pixelsWide: 8256, pixelsHigh: 5504, pixelPitchMicrometers: 4.36, peakQuantumEfficiency: .8,
|
|
38
|
+
fullWellElectrons: 69000, readNoise: { lowGainElectrons: 3.5, highGainElectrons: 1.5, highGainFromIso: 250 }, highGainFullWellElectrons: 22080,
|
|
39
|
+
adcBitDepth: 14, baseIso: 80, maximumIso: 25600, maximumAnalogGain: 64, readoutSeconds: .005, fixedPattern: BSI,
|
|
40
|
+
goodFor: ["landscape", "studio", "wildlife"], description: "High resolution with a fast stacked readout: fine detail for landscape and studio work, little rolling-shutter skew for action.",
|
|
41
|
+
fixedLenses: null, provenance: { pixelPitchMicrometers: "derived", fullWellElectrons: "design (S2 scaled by pixel area)", readNoise: "design (S2)", highGainFromIso: "design (S2)",
|
|
42
|
+
highGainFullWellElectrons: "derived", baseIso: "derived" } },
|
|
43
|
+
{ id: "full-frame-24mp-stacked-sports-2020s", name: "Full frame 24 MP stacked (sports), 2020s", marketTag: "professional", architecture: "stacked",
|
|
44
|
+
sensorWidthMm: 36, sensorHeightMm: 24, pixelsWide: 6000, pixelsHigh: 4000, pixelPitchMicrometers: 6, peakQuantumEfficiency: .8,
|
|
45
|
+
fullWellElectrons: 80000, readNoise: { lowGainElectrons: 3, highGainElectrons: 1, highGainFromIso: 400 }, highGainFullWellElectrons: 25000,
|
|
46
|
+
adcBitDepth: 14, baseIso: 125, maximumIso: 102400, maximumAnalogGain: 64, readoutSeconds: .0025, fixedPattern: BSI,
|
|
47
|
+
goodFor: ["sports", "wildlife", "journalism"], description: "The fastest readout and low high-gain read noise: almost no rolling-shutter skew and clean high ISO, at a moderate resolution.",
|
|
48
|
+
fixedLenses: null, provenance: { pixelPitchMicrometers: "derived", highGainFullWellElectrons: "derived", baseIso: "derived" } },
|
|
49
|
+
{ id: "full-frame-24mp-fsi-2010s", name: "Full frame 24 MP front-illuminated, 2010s", marketTag: "professional", architecture: "front-illuminated",
|
|
50
|
+
sensorWidthMm: 36.01, sensorHeightMm: 24.07, pixelsWide: 6032, pixelsHigh: 4032, pixelPitchMicrometers: 5.97, peakQuantumEfficiency: .53,
|
|
51
|
+
fullWellElectrons: 76000, readNoise: { lowGainElectrons: 4, highGainElectrons: 4, highGainFromIso: null }, highGainFullWellElectrons: null,
|
|
52
|
+
adcBitDepth: 14, baseIso: 80, maximumIso: 25600, maximumAnalogGain: 64, readoutSeconds: .08, fixedPattern: FSI,
|
|
53
|
+
goodFor: ["portrait", "landscape", "journalism"], description: "An older professional sensor: lower QE and one conversion gain, so a modern consumer BSI sensor beats it at high ISO; slow readout.",
|
|
54
|
+
fixedLenses: null, provenance: { ...sourced("S3", "pixels", "pixelPitchMicrometers", "peakQuantumEfficiency", "fullWellElectrons", "adcBitDepth"),
|
|
55
|
+
sensorSize: "derived", architecture: "derived S4", baseIso: "derived" } },
|
|
56
|
+
{ id: "aps-c-24mp-fsi-2020s", name: "APS-C 24 MP front-illuminated, 2020s", marketTag: "consumer", architecture: "front-illuminated",
|
|
57
|
+
sensorWidthMm: 23.55, sensorHeightMm: 15.73, pixelsWide: 6024, pixelsHigh: 4024, pixelPitchMicrometers: 3.91, peakQuantumEfficiency: .55,
|
|
58
|
+
fullWellElectrons: 36000, readNoise: { lowGainElectrons: 2.7, highGainElectrons: 2.7, highGainFromIso: null }, highGainFullWellElectrons: null,
|
|
59
|
+
adcBitDepth: 14, baseIso: 80, maximumIso: 25600, maximumAnalogGain: 64, readoutSeconds: .06, fixedPattern: FSI,
|
|
60
|
+
goodFor: ["travel", "family", "learning"], description: "A smaller, front-illuminated sensor: less light per pixel, a slower readout and more rolling-shutter skew.",
|
|
61
|
+
fixedLenses: null, provenance: { ...sourced("S5", "pixels", "pixelPitchMicrometers", "architecture", "fullWellElectrons", "readNoise", "adcBitDepth"), sensorSize: "derived", baseIso: "derived" } },
|
|
62
|
+
{ id: "aps-c-26mp-bsi-2020s", name: "APS-C 26 MP BSI, 2020s", marketTag: "professional", architecture: "back-illuminated",
|
|
63
|
+
sensorWidthMm: 23.49, sensorHeightMm: 15.7, pixelsWide: 6248, pixelsHigh: 4176, pixelPitchMicrometers: 3.76, peakQuantumEfficiency: .8,
|
|
64
|
+
fullWellElectrons: 50000, readNoise: { lowGainElectrons: 3.3, highGainElectrons: 1, highGainFromIso: 250 }, highGainFullWellElectrons: 16000,
|
|
65
|
+
adcBitDepth: 14, baseIso: 80, maximumIso: 25600, maximumAnalogGain: 64, readoutSeconds: .025, fixedPattern: BSI,
|
|
66
|
+
goodFor: ["wildlife", "sports", "travel"], description: "A BSI APS-C sensor with dual conversion gain whose readout beats the consumer full-frame profile's.",
|
|
67
|
+
fixedLenses: null, provenance: { ...sourced("S6", "pixels", "pixelPitchMicrometers", "peakQuantumEfficiency", "fullWellElectrons", "readNoise"), sensorSize: "derived",
|
|
68
|
+
architecture: "published S6", highGainFromIso: "design (S7)", highGainFullWellElectrons: "derived", baseIso: "derived" } },
|
|
69
|
+
{ id: "medium-format-100mp-bsi-16bit", name: "Medium format 100 MP BSI, 16-bit", marketTag: "professional", architecture: "back-illuminated",
|
|
70
|
+
sensorWidthMm: 43.856, sensorHeightMm: 32.9, pixelsWide: 11656, pixelsHigh: 8750, pixelPitchMicrometers: 3.76, peakQuantumEfficiency: .8,
|
|
71
|
+
fullWellElectrons: 50300, readNoise: { lowGainElectrons: 3.3, highGainElectrons: 1, highGainFromIso: 250 }, highGainFullWellElectrons: 16096,
|
|
72
|
+
adcBitDepth: 16, baseIso: 80, maximumIso: 12800, maximumAnalogGain: 64, readoutSeconds: .25, fixedPattern: BSI,
|
|
73
|
+
goodFor: ["studio", "landscape", "portrait"], description: "A large sensor with 100 MP and a 16-bit RAW; a slow readout makes its electronic shutter unsuited to motion.",
|
|
74
|
+
fixedLenses: null, provenance: { ...sourced("S7", "pixels", "pixelPitchMicrometers", "sensorSize", "fullWellElectrons", "readNoise", "adcBitDepth"), architecture: "published S8",
|
|
75
|
+
highGainFromIso: "derived S7", highGainFullWellElectrons: "derived", baseIso: "derived" } },
|
|
76
|
+
{ id: "phone-50mp-stacked", name: "Phone, large, 50 MP stacked", marketTag: "consumer", architecture: "stacked",
|
|
77
|
+
sensorWidthMm: 9.844, sensorHeightMm: 7.431, pixelsWide: 8192, pixelsHigh: 6144, pixelPitchMicrometers: 1.197, peakQuantumEfficiency: .75,
|
|
78
|
+
fullWellElectrons: 8600, readNoise: { lowGainElectrons: 3, highGainElectrons: 1.4, highGainFromIso: 160 }, highGainFullWellElectrons: 2150,
|
|
79
|
+
adcBitDepth: 12, baseIso: 40, maximumIso: 6400, maximumAnalogGain: 64, readoutSeconds: 1 / 30,
|
|
80
|
+
fixedPattern: { prnu: .008, dsnu: .3, hotPixelFraction: 2e-4, hotPixelMultiplier: 50, rowElectrons: .2, columnElectrons: .2 },
|
|
81
|
+
goodFor: ["everyday", "travel", "family"], description: "A large phone sensor: tiny pixels with a small full well, so less dynamic range and more noise per pixel; simulated as a single frame (no multi-frame stacking).",
|
|
82
|
+
fixedLenses: "phone", provenance: { ...sourced("S9", "pixels", "pixelPitchMicrometers", "sensorSize", "architecture", "adcBitDepth"), fullWellElectrons: "design (S10 scaled by pixel area)",
|
|
83
|
+
readNoise: "high gain published S10, low gain design", highGainFullWellElectrons: "derived", readoutSeconds: "derived S9 (1 / full-resolution frame rate)",
|
|
84
|
+
fixedPattern: "PRNU, row and column published S10; the rest design", baseIso: "derived" } }
|
|
85
|
+
] satisfies SensorProfile[]).map((p) => Object.freeze(p)))
|
|
86
|
+
```
|