@photivra/engine 2.21.0 → 2.22.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 +118 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +11 -0
- package/dist/capture/c2pa-embed.d.ts +116 -0
- package/dist/capture/c2pa-embed.js +493 -0
- package/dist/capture/c2pa-embed.js.map +1 -0
- package/dist/capture/dng-import.d.ts +73 -8
- package/dist/capture/dng-import.js +315 -80
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/dng-opcodes.d.ts +96 -0
- package/dist/capture/dng-opcodes.js +391 -0
- package/dist/capture/dng-opcodes.js.map +1 -0
- package/dist/capture/dng-profile-maps.d.ts +99 -0
- package/dist/capture/dng-profile-maps.js +381 -0
- package/dist/capture/dng-profile-maps.js.map +1 -0
- package/dist/capture/dng-stage-image.d.ts +23 -0
- package/dist/capture/dng-stage-image.js +65 -0
- package/dist/capture/dng-stage-image.js.map +1 -0
- package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
- package/dist/capture/jpeg-xl-tiles.js +151 -0
- package/dist/capture/jpeg-xl-tiles.js.map +1 -0
- package/dist/capture/lossy-jpeg.d.ts +17 -0
- package/dist/capture/lossy-jpeg.js +369 -0
- package/dist/capture/lossy-jpeg.js.map +1 -0
- package/dist/capture/native-raw-development.d.ts +2 -1
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
- package/dist/color/neighbor-highlight-reconstruction.js +234 -0
- package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
- package/dist/color/sensor-color-development.d.ts +5 -0
- package/dist/color/sensor-color-development.js +7 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/color/spectral-spikiness.d.ts +11 -0
- package/dist/color/spectral-spikiness.js +23 -0
- package/dist/color/spectral-spikiness.js.map +1 -0
- package/dist/core/execution-error.d.ts +21 -0
- package/dist/core/execution-error.js +12 -0
- package/dist/core/execution-error.js.map +1 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/catalogue-lens-signal.d.ts +26 -1
- package/dist/facade/catalogue-lens-signal.js +25 -7
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +1 -1
- package/dist/facade/diffraction-provider.js +4 -4
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +38 -6
- 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.map +1 -1
- package/dist/facade/display-view-normal.d.ts +15 -0
- package/dist/facade/display-view-normal.js +44 -0
- package/dist/facade/display-view-normal.js.map +1 -0
- package/dist/facade/display-view-rendering.d.ts +97 -0
- package/dist/facade/display-view-rendering.js +598 -0
- package/dist/facade/display-view-rendering.js.map +1 -0
- package/dist/facade/display-view.d.ts +37 -65
- package/dist/facade/display-view.js +147 -476
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.js +2 -2
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/fast-methods.d.ts +1 -1
- package/dist/facade/fast-methods.js +37 -8
- package/dist/facade/fast-methods.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +1 -1
- package/dist/facade/generic-lens-capture.js +1 -1
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane.js +41 -10
- package/dist/facade/image-plane.js.map +1 -1
- package/dist/facade/on-demand-kits.d.ts +14 -0
- package/dist/facade/on-demand-kits.js +36 -0
- package/dist/facade/on-demand-kits.js.map +1 -0
- package/dist/facade/opt-in-kit.d.ts +9 -0
- package/dist/facade/opt-in-kit.js +11 -0
- package/dist/facade/opt-in-kit.js.map +1 -0
- package/dist/facade/quality-kit.d.ts +21 -0
- package/dist/facade/quality-kit.js +23 -0
- package/dist/facade/quality-kit.js.map +1 -0
- package/dist/facade/quality-paths.d.ts +22 -0
- package/dist/facade/quality-paths.js +44 -0
- package/dist/facade/quality-paths.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +18 -2
- package/dist/facade/scene-radiance.js +6 -4
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +32 -13
- package/dist/facade/simulate-capture.js +162 -69
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +10 -12
- package/dist/facade/simulate-display-view.js +57 -48
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
- package/dist/gpu/display-view-gpu-renderer.js +134 -0
- package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
- package/dist/gpu/display-view-image-material-module.d.ts +3 -2
- package/dist/gpu/display-view-image-material-module.js +25 -3
- package/dist/gpu/display-view-image-material-module.js.map +1 -1
- package/dist/gpu/display-view-image-quality.d.ts +43 -1
- package/dist/gpu/display-view-image-quality.js +256 -11
- package/dist/gpu/display-view-image-quality.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +13 -64
- package/dist/gpu/display-view-quality-gpu.js +60 -249
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +16 -16
- package/dist/gpu/display-view-speed-gpu.js +50 -12
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-accelerator.js +3 -0
- package/dist/gpu/image-kernel-accelerator.js.map +1 -1
- package/dist/gpu/image-kernel-execution.d.ts +2 -13
- package/dist/gpu/image-kernel-execution.js +2 -10
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
- package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
- package/dist/gpu/wgsl-display-view-coupled.js +353 -0
- package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -9
- package/dist/gpu/wgsl-display-view-quality-kernel.js +109 -111
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +1 -41
- package/dist/gpu/wgsl-display-view-speed-kernel.js +71 -8
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/output/camera-channel-response.d.ts +50 -0
- package/dist/output/camera-channel-response.js +69 -0
- package/dist/output/camera-channel-response.js.map +1 -0
- package/dist/output/native-capture-sdr.js +5 -2
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/sdr-rendering.d.ts +18 -1
- package/dist/output/sdr-rendering.js +39 -2
- package/dist/output/sdr-rendering.js.map +1 -1
- package/dist/sensor/native-sensor-report.d.ts +10 -0
- package/dist/sensor/native-sensor-report.js +16 -0
- package/dist/sensor/native-sensor-report.js.map +1 -0
- package/dist/sensor/sensor-characteristics.d.ts +81 -13
- package/dist/sensor/sensor-characteristics.js +10 -6
- package/dist/sensor/sensor-characteristics.js.map +1 -1
- package/docs/API_REFERENCE.md +117 -4
- package/docs/QUICK_START.md +37 -8
- package/docs/USAGE.md +104 -22
- package/docs/api/capture-c2pa-embed.md +177 -0
- package/docs/api/capture-dng-import.md +86 -10
- package/docs/api/capture-dng-opcodes.md +93 -0
- package/docs/api/capture-dng-profile-maps.md +141 -0
- package/docs/api/capture-dng-stage-image.md +43 -0
- package/docs/api/capture-jpeg-xl-tiles.md +33 -0
- package/docs/api/capture-lossy-jpeg.md +41 -0
- package/docs/api/capture-native-raw-development.md +2 -1
- package/docs/api/core-execution-error.md +43 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +255 -5
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-display-view.md +11 -15
- package/docs/api/facade-fast-methods.md +38 -9
- package/docs/api/facade-quality-paths.md +24 -0
- package/docs/api/facade-scene-radiance.md +1 -1
- package/docs/api/facade-simulate-capture.md +24 -7
- package/docs/api/facade-simulate-display-view.md +10 -12
- package/docs/api/gpu-image-kernel-execution.md +0 -36
- package/docs/api/output-camera-channel-response.md +55 -0
- package/docs/api/output-sdr-rendering.md +6 -1
- package/docs/api/sensor-sensor-characteristics.md +54 -2
- package/llms.txt +1 -0
- package/package.json +6 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SensorReadoutConversionProfile } from "./raw-readout.js";
|
|
1
|
+
import { type SensorReadoutConversionProfile, type SensorReadoutConversionRegime } from "./raw-readout.js";
|
|
2
2
|
import type { SensorFixedPatternNoiseProfile } from "./fixed-pattern-noise.js";
|
|
3
3
|
/**
|
|
4
4
|
* #555: sensor characteristics a capture can set one at a time (`simulateCapture({ sensor })`). Each is off by default: an
|
|
@@ -24,7 +24,9 @@ export interface SimulateCaptureSensorCharacteristics {
|
|
|
24
24
|
referencePixelPitchMicrometers?: number;
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
|
-
* Read noise, RMS e⁻ (0–100), of
|
|
27
|
+
* Read noise, RMS e⁻ (0–100), of one physical photosite, as a sensor datasheet gives it (2.22.0: on a tier too; a rendered site that
|
|
28
|
+
* sums N photosites, above the tier's 6 µm or a profile's own pitch, takes it ×√N), of the low and the high conversion gain, and
|
|
29
|
+
* the dual-gain switch: the ISO setting from
|
|
28
30
|
* which the high conversion gain is used (any ISO from 100; above the body's highest ISO, the low gain is used throughout).
|
|
29
31
|
* An omitted field keeps the body's value. Needs readout regimes that declare their exposure index (the tiers do).
|
|
30
32
|
*/
|
|
@@ -103,15 +105,73 @@ export interface SimulateCaptureSensorReport {
|
|
|
103
105
|
* the figure no longer describes the image. Computed only when the request resolves to exact (`fast: false`, the default for
|
|
104
106
|
* DNG, JPEG and PNG exports; omitted for a fast request).
|
|
105
107
|
*/
|
|
106
|
-
midGrey?:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
midGrey?: SimulateCaptureMidGreyFigure;
|
|
109
|
+
/**
|
|
110
|
+
* #595 (2.22.0): the same figures for one of the body's native photosites, whatever pitch the capture renders (the fields above
|
|
111
|
+
* are the as-rendered site's). The native pitch is a generic tier's (or a caller preset's) declared 6 µm reference, or a sensor
|
|
112
|
+
* profile's own photosite; the request's sensor characteristics apply as they would at that pitch. Exact arithmetic under the
|
|
113
|
+
* engine's model (model figures, not measurements); see docs/QUICK_START.md, "Native-pixel dynamic range (#595)".
|
|
114
|
+
*/
|
|
115
|
+
native: SimulateCaptureNativeSensorReport;
|
|
116
|
+
}
|
|
117
|
+
/** #535: the mid-grey noise figure of {@link SimulateCaptureSensorReport.midGrey}. */
|
|
118
|
+
export interface SimulateCaptureMidGreyFigure {
|
|
119
|
+
reflectance: 0.18;
|
|
120
|
+
channel: "green";
|
|
121
|
+
signalElectrons: number;
|
|
122
|
+
noiseElectrons: SimulateCaptureNoiseComponents;
|
|
123
|
+
snr: number;
|
|
124
|
+
snrDecibels: number;
|
|
125
|
+
clipped: boolean;
|
|
126
|
+
}
|
|
127
|
+
/** #595: {@link SimulateCaptureSensorReport.native}, the figures of one native photosite. */
|
|
128
|
+
export interface SimulateCaptureNativeSensorReport {
|
|
129
|
+
/** The native photosite pitch, µm (tiers: 6; a profile: its own). */
|
|
130
|
+
pixelPitchMicrometers: number;
|
|
131
|
+
/** N = (rendered pitch / native pitch)²: the photosites one rendered site stands for (below 1, a site smaller than a photosite). */
|
|
132
|
+
photositesPerSite: number;
|
|
133
|
+
/** Full-well capacity of one photosite, e⁻. */
|
|
134
|
+
fullWellElectrons: number;
|
|
135
|
+
/** Read noise of one photosite at the capture's ISO, RMS e⁻ (the capture's readout regime). */
|
|
136
|
+
readNoiseElectrons: number;
|
|
137
|
+
/** Engineering dynamic range of one photosite at the capture's ISO, stops (the definition of `dynamicRangeStops`). */
|
|
138
|
+
dynamicRangeStops: number;
|
|
139
|
+
/** The same per ISO setting of the body. */
|
|
140
|
+
dynamicRangeByIso: readonly {
|
|
141
|
+
iso: number;
|
|
142
|
+
stops: number;
|
|
143
|
+
}[];
|
|
144
|
+
/**
|
|
145
|
+
* EMVA 1288 Release 4.0 (§2.7, Eq. 26–28) at the capture's ISO: the absolute sensitivity threshold μ_e.min = √(σ_d² + σ_q² + 1/4) + 1/2
|
|
146
|
+
* (σ_d the read noise, dark current excluded; σ_q the quantization noise in e⁻), DR = μ_e.sat / μ_e.min in stops and dB, and the
|
|
147
|
+
* saturation capacity per area μ_e.sat / p² (Table 2).
|
|
148
|
+
*/
|
|
149
|
+
emva1288: {
|
|
150
|
+
absoluteSensitivityThresholdElectrons: number;
|
|
151
|
+
dynamicRangeStops: number;
|
|
152
|
+
dynamicRangeDecibels: number;
|
|
153
|
+
saturationCapacityElectronsPerSquareMicrometer: number;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* A convention, not part of EMVA 1288: the dynamic range of the whole frame's native photosites resampled to 8 million output
|
|
157
|
+
* pixels, each summing framePhotosites / 8e6 photosites with their read noise (EMVA 1288 R4.0 §6.4, binning):
|
|
158
|
+
* DR + ½·log₂(framePhotosites / 8e6), for the engineering and the EMVA figure. No ADC limit is applied to the sum.
|
|
159
|
+
*/
|
|
160
|
+
normalized8Megapixel: {
|
|
161
|
+
referencePixels: 8_000_000;
|
|
162
|
+
framePhotosites: number;
|
|
163
|
+
dynamicRangeStops: number;
|
|
164
|
+
emva1288DynamicRangeStops: number;
|
|
114
165
|
};
|
|
166
|
+
/** The mid-grey figure for one photosite (its charge (native/rendered pitch)² of the site's), present when `midGrey` is. */
|
|
167
|
+
midGrey?: SimulateCaptureMidGreyFigure;
|
|
168
|
+
}
|
|
169
|
+
/** #595: what {@link sensorReport} needs for the native figures: the readout as characterized at the native pitch. */
|
|
170
|
+
export interface NativeSensorInput {
|
|
171
|
+
profile: SensorReadoutConversionProfile;
|
|
172
|
+
capacityElectrons: number;
|
|
173
|
+
pixelPitchMicrometers: number;
|
|
174
|
+
framePhotosites: number;
|
|
115
175
|
}
|
|
116
176
|
/** The request's characteristics, range-checked, with the EQE scale peakQuantumEfficiency / 0.5. */
|
|
117
177
|
export type ParsedSensorCharacteristics = SimulateCaptureSensorCharacteristics & {
|
|
@@ -136,12 +196,20 @@ export declare function declaredReadNoise(profile: SensorReadoutConversionProfil
|
|
|
136
196
|
* e⁻ per code, and pre-ADC saturation, e⁻ equivalent) multiplied by `chargeScale`, so a full well `chargeScale` times the
|
|
137
197
|
* body's reaches the same white code; with `adcBitDepth`, the black level ×2^Δbits (rounded), the saturation code 2^bits − 1
|
|
138
198
|
* and the conversion gain over the new code span; with `readNoise`, one electronic read-noise component per regime, the high
|
|
139
|
-
* conversion gain's from `highGainFromIso
|
|
199
|
+
* conversion gain's from `highGainFromIso`; every read noise (the body's or the request's) times `readNoiseScale` (#599: √N for a site
|
|
200
|
+
* that sums N photosites, as the sensor-profile path does). Codes per unit charge of the base regime otherwise, digital gain and the ISO
|
|
140
201
|
* selection are unchanged. Re-checked by the readout parser. With nothing to change, the profile itself.
|
|
141
202
|
*/
|
|
142
|
-
export declare function characterizeReadout(profile: SensorReadoutConversionProfile, chargeScale: number, c?: ParsedSensorCharacteristics): SensorReadoutConversionProfile;
|
|
203
|
+
export declare function characterizeReadout(profile: SensorReadoutConversionProfile, chargeScale: number, c?: ParsedSensorCharacteristics, readNoiseScale?: number): SensorReadoutConversionProfile;
|
|
204
|
+
/** Charge at the RAW white level and the read-plus-quantization floor of one regime, e⁻. */
|
|
205
|
+
export declare function regimeRange(profile: SensorReadoutConversionProfile, regime: SensorReadoutConversionRegime, capacityElectrons: number): {
|
|
206
|
+
saturation: number;
|
|
207
|
+
quantization: number;
|
|
208
|
+
read: number;
|
|
209
|
+
};
|
|
143
210
|
/**
|
|
144
211
|
* The report: the characteristics used, the dynamic range per ISO, and the mid-grey noise figure for `greyElectrons`, the
|
|
145
|
-
* green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge.
|
|
212
|
+
* green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge. `native` (#595) is added by
|
|
213
|
+
* `nativeSensorReport` (native-sensor-report.ts).
|
|
146
214
|
*/
|
|
147
215
|
export declare function sensorReport(profile: SensorReadoutConversionProfile, regimeId: string, capacityElectrons: number, greyElectrons: number | undefined, darkElectrons: number, base: Pick<SimulateCaptureSensorReport, "pixelPitchMicrometers" | "peakQuantumEfficiency" | "readoutSeconds">, pattern?: SensorFixedPatternNoiseProfile): SimulateCaptureSensorReport;
|
|
@@ -72,12 +72,13 @@ export function declaredReadNoise(profile) {
|
|
|
72
72
|
* e⁻ per code, and pre-ADC saturation, e⁻ equivalent) multiplied by `chargeScale`, so a full well `chargeScale` times the
|
|
73
73
|
* body's reaches the same white code; with `adcBitDepth`, the black level ×2^Δbits (rounded), the saturation code 2^bits − 1
|
|
74
74
|
* and the conversion gain over the new code span; with `readNoise`, one electronic read-noise component per regime, the high
|
|
75
|
-
* conversion gain's from `highGainFromIso
|
|
75
|
+
* conversion gain's from `highGainFromIso`; every read noise (the body's or the request's) times `readNoiseScale` (#599: √N for a site
|
|
76
|
+
* that sums N photosites, as the sensor-profile path does). Codes per unit charge of the base regime otherwise, digital gain and the ISO
|
|
76
77
|
* selection are unchanged. Re-checked by the readout parser. With nothing to change, the profile itself.
|
|
77
78
|
*/
|
|
78
|
-
export function characterizeReadout(profile, chargeScale, c) {
|
|
79
|
+
export function characterizeReadout(profile, chargeScale, c, readNoiseScale = 1) {
|
|
79
80
|
const bits = c?.adcBitDepth, n = c?.readNoise, declared = n && declaredReadNoise(profile);
|
|
80
|
-
if (chargeScale === 1 && !bits && !n)
|
|
81
|
+
if (chargeScale === 1 && !bits && !n && readNoiseScale === 1)
|
|
81
82
|
return profile;
|
|
82
83
|
return parseSensorReadoutConversionProfile({ ...profile, regimes: profile.regimes.map((regime) => {
|
|
83
84
|
const adc = regime.adc, rescale = bits !== undefined && bits !== adc.bitDepth;
|
|
@@ -89,15 +90,17 @@ export function characterizeReadout(profile, chargeScale, c) {
|
|
|
89
90
|
if (n) {
|
|
90
91
|
const gain = regime.exposureIndexGain ?? fail("sensor.readNoise needs readout regimes that declare their exposure index.");
|
|
91
92
|
const from = n.highGainFromIso ?? declared.highGainFromIso ?? Infinity, high = gain.exposureIndex >= from;
|
|
92
|
-
const value = high ? n.highGainElectrons ?? declared.highGainElectrons : n.lowGainElectrons ?? declared.lowGainElectrons;
|
|
93
|
+
const value = (high ? n.highGainElectrons ?? declared.highGainElectrons : n.lowGainElectrons ?? declared.lowGainElectrons) * readNoiseScale;
|
|
93
94
|
out.readNoiseComponents = [{ componentId: "electronic", rmsElectrons: { value, evidence: regime.evidence }, evidence: regime.evidence }];
|
|
94
95
|
out.exposureIndexGain = { ...gain, conversionGainMode: high ? "high-conversion-gain" : "low-conversion-gain" };
|
|
95
96
|
}
|
|
97
|
+
else if (readNoiseScale !== 1)
|
|
98
|
+
out.readNoiseComponents = regime.readNoiseComponents.map((component) => ({ ...component, rmsElectrons: { ...component.rmsElectrons, value: component.rmsElectrons.value * readNoiseScale } }));
|
|
96
99
|
return out;
|
|
97
100
|
}) });
|
|
98
101
|
}
|
|
99
102
|
/** Charge at the RAW white level and the read-plus-quantization floor of one regime, e⁻. */
|
|
100
|
-
function regimeRange(profile, regime, capacityElectrons) {
|
|
103
|
+
export function regimeRange(profile, regime, capacityElectrons) {
|
|
101
104
|
const adc = regime.adc, gain = regime.systemConversionGainElectronsPerCode.value, digitalGain = adc.digitalGain ?? 1;
|
|
102
105
|
const white = calculateSiteEffectiveSaturationCode({ physicalChargeCapacityElectrons: capacityElectrons, preAdcSaturationElectronEquivalent: regime.preAdcSaturationElectronEquivalent.value,
|
|
103
106
|
systemConversionGainElectronsPerCode: gain, blackLevelCode: adc.blackLevelCode, digitalSaturationCode: adc.digitalSaturationCode, adcMaximumCode: 2 ** adc.bitDepth - 1, digitalGain }, profile, regime.regimeId);
|
|
@@ -107,7 +110,8 @@ function regimeRange(profile, regime, capacityElectrons) {
|
|
|
107
110
|
}
|
|
108
111
|
/**
|
|
109
112
|
* The report: the characteristics used, the dynamic range per ISO, and the mid-grey noise figure for `greyElectrons`, the
|
|
110
|
-
* green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge.
|
|
113
|
+
* green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge. `native` (#595) is added by
|
|
114
|
+
* `nativeSensorReport` (native-sensor-report.ts).
|
|
111
115
|
*/
|
|
112
116
|
export function sensorReport(profile, regimeId, capacityElectrons, greyElectrons, darkElectrons, base, pattern) {
|
|
113
117
|
const stops = (r) => { const q = regimeRange(profile, r, capacityElectrons); return Math.log2(q.saturation / Math.hypot(q.read, q.quantization)); };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensor-characteristics.js","sourceRoot":"","sources":["../../src/sensor/sensor-characteristics.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Adjustable sensor characteristics for `simulateCapture` (#555) and the capture's noise figure (#535): the request\n * options that replace a camera body's sensor values one at a time, the readout they give, and the calculated dynamic\n * range and mid-grey signal-to-noise ratio the result reports.\n *\n * Everything here works on the body's declared readout regimes, so it applies to a generic tier and to a caller's\n * preset record alike, and a later sensor profile (#554) can supply the same options as data.\n * @see docs/QUICK_START.md, \"Sensor characteristics (#555)\", for the options, the equations and their sources.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireInputObject } from \"../core/record-validation.js\";\nimport { calculateSiteEffectiveSaturationCode } from \"../capture/raw-white-level.js\";\nimport { parseSensorReadoutConversionProfile, type SensorReadoutConversionProfile, type SensorReadoutConversionRegime } from \"./raw-readout.js\";\nimport type { SensorFixedPatternNoiseProfile } from \"./fixed-pattern-noise.js\";\n\n/**\n * #555: sensor characteristics a capture can set one at a time (`simulateCapture({ sensor })`). Each is off by default: an\n * omitted field keeps the camera body's value, and a request with none of them is byte-identical to one before 2.20.0.\n * Every value set is listed in the result's `assumptions`.\n */\nexport interface SimulateCaptureSensorCharacteristics {\n /**\n * Peak external quantum efficiency of the green channel (its value at 555 nm), 0.05–0.9. Default 0.5, the facade's\n * idealized sensor. Every channel's EQE curve is multiplied by `peakQuantumEfficiency / 0.5`, so the curves keep their\n * shape (and the colour matrix is unchanged) and the collected charge scales in proportion.\n */\n peakQuantumEfficiency?: number;\n /**\n * Full-well capacity: the charge, e⁻, that reaches the RAW white level at base ISO. Default: the body's base-ISO saturation\n * at 6 µm, scaled by the pixel area (the `sensor.isoAreaNormalization` approximation). `link` `\"pixel-area\"` (default)\n * scales `electrons`, the full well at `referencePixelPitchMicrometers` (default 6), by (pitch / reference)²; `\"none\"`\n * uses `electrons` at any pitch. The result is 100 e⁻ to 1e10 e⁻.\n */\n fullWell?: { electrons: number; link?: \"pixel-area\" | \"none\"; referencePixelPitchMicrometers?: number };\n /**\n * Read noise, RMS e⁻ (0–100), of the low and the high conversion gain, and the dual-gain switch: the ISO setting from\n * which the high conversion gain is used (any ISO from 100; above the body's highest ISO, the low gain is used throughout).\n * An omitted field keeps the body's value. Needs readout regimes that declare their exposure index (the tiers do).\n */\n readNoise?: { lowGainElectrons?: number; highGainElectrons?: number; highGainFromIso?: number };\n /** Readout time, s (1e-6 to 1): the rolling shutter's top-to-bottom skew. Default: the body's (tiers 8, 15 and 30 ms). */\n readoutSeconds?: number;\n /**\n * ADC bit depth: 12, 14 or 16. Default: the body's (consumer 12, the others 14). The black level scales with the code range\n * (×2^Δbits), and the conversion gain follows so the same charge reaches the same fraction of the range.\n */\n adcBitDepth?: 12 | 14 | 16;\n /**\n * Opt-in (advanced): the body's synthetic fixed-pattern noise profile (PRNU, DSNU, hot pixels, row and column offsets; EMVA\n * 1288 Release 4.0 non-uniformities), the same pattern in every capture. Default false. Not in `simulateDisplayView`.\n */\n fixedPatternNoise?: boolean;\n}\n\n/** The option keys of {@link SimulateCaptureSensorCharacteristics}, in `sensor`. */\nexport const SENSOR_CHARACTERISTIC_KEYS = [\"peakQuantumEfficiency\", \"fullWell\", \"readNoise\", \"readoutSeconds\", \"adcBitDepth\", \"fixedPatternNoise\"] as const;\n\n/** One RMS noise component of the mid-grey figure, e⁻ (input-referred). */\nexport interface SimulateCaptureNoiseComponents {\n /** Photon shot noise √S (Poisson). */\n photonShot: number;\n /** Read noise of the capture's readout regime. */\n read: number;\n /** Dark-current shot noise √(D·t), D = 1 e⁻/s at 20 °C. */\n darkCurrent: number;\n /** ADC quantization, one code (and one more after a digital gain) as a uniform 1/12 code² variance, in e⁻. */\n quantization: number;\n /** With `sensor.fixedPatternNoise`: √((PRNU·S)² + (DSNU·D·t)² + row² + column²), hot pixels excluded; a spatial, not temporal, noise. */\n fixedPattern?: number;\n /** √ of the sum of the squares. */\n total: number;\n}\n\n/**\n * #555, #535: the capture's sensor characteristics as used, and the figures calculated from them (reported, not settable).\n * Model figures from the engine's own noise model, not measurements.\n */\nexport interface SimulateCaptureSensorReport {\n pixelPitchMicrometers: number;\n /** The green channel's peak EQE (at 555 nm). */\n peakQuantumEfficiency: number;\n /** Full-well capacity, e⁻: the base-ISO saturation charge (`sensor.capacityElectrons`). */\n fullWellElectrons: number;\n /** Read noise, RMS e⁻, of each conversion gain, and the ISO from which the high gain is used (null: one gain throughout). */\n readNoise: { lowGainElectrons: number; highGainElectrons: number; highGainFromIso: number | null };\n /** The body's rolling readout skew, s (null when it declares no rolling readout). */\n readoutSeconds: number | null;\n /** ADC bit depth of the capture's readout regime. */\n adcBitDepth: number;\n fixedPatternNoise: boolean;\n /**\n * Engineering dynamic range at the capture's ISO, stops: log₂(S_sat / σ_floor), with S_sat the charge at the RAW white\n * level (full well over the analog gain, limited by the charge capacity and the ADC range) and σ_floor the read noise and\n * ADC quantization added in quadrature (the temporal dark noise of EMVA 1288 Release 4.0 without dark current). An ideal\n * noiseless readout is limited by the ADC alone: log₂((white − black)·√12) for unit digital gain.\n */\n dynamicRangeStops: number;\n /** The same per ISO setting of the body, in its order. */\n dynamicRangeByIso: readonly { iso: number; stops: number }[];\n /**\n * #535: the expected signal-to-noise ratio of an 18 % Lambertian grey patch under the request's illuminant and illuminance\n * at the capture's exposure, ISO and sensor, on the green channel next to the optical axis (no vignetting, PSF or motion).\n * `snr` = S/σ_total and `snrDecibels` = 20·log₁₀(snr). `clipped`: S is at or above the white level, where the RAW clips and\n * the figure no longer describes the image. Computed only when the request resolves to exact (`fast: false`, the default for\n * DNG, JPEG and PNG exports; omitted for a fast request).\n */\n midGrey?: { reflectance: 0.18; channel: \"green\"; signalElectrons: number; noiseElectrons: SimulateCaptureNoiseComponents; snr: number; snrDecibels: number; clipped: boolean };\n}\n\n/** The request's characteristics, range-checked, with the EQE scale peakQuantumEfficiency / 0.5. */\nexport type ParsedSensorCharacteristics = SimulateCaptureSensorCharacteristics & { qeScale: number };\n\n/**\n * A field's check: [minimum, maximum] (a number), the values allowed, 0 (a nested record) or a predicate. #554 shares the checker\n * with the sensor profile parser.\n */\nexport type FieldRange = readonly [number, number] | readonly unknown[] | 0 | ((value: unknown) => boolean);\n/** Accepted ranges (design limits) and values of each characteristic, by path below `sensor`. */\nconst RANGES: Readonly<Record<string, FieldRange>> = { peakQuantumEfficiency: [0.05, 0.9], fullWell: 0, \"fullWell.electrons\": [100, 1e10],\n \"fullWell.link\": [\"pixel-area\", \"none\"], \"fullWell.referencePixelPitchMicrometers\": [0.5, 1000], readNoise: 0, \"readNoise.lowGainElectrons\": [0, 100],\n \"readNoise.highGainElectrons\": [0, 100], \"readNoise.highGainFromIso\": [100, 1e7], readoutSeconds: [1e-6, 1], adcBitDepth: [12, 14, 16], fixedPatternNoise: [true, false] };\nfunction fail(message: string): never { throw new InvalidConfigurationError(message); }\n/**\n * Checks `keys` of `record` against `ranges` (by path below `root`; nested records by \"a.b\"). An undefined field is skipped, or\n * with `required` refused, and with `required` every field `ranges` lists at that level is checked.\n */\nexport function checkFieldRanges(record: Record<string, unknown>, keys: readonly string[], prefix: string, ranges: Readonly<Record<string, FieldRange>> = RANGES, root = \"sensor.\", required = false): void {\n const all = required ? [...new Set([...keys, ...Object.keys(ranges).filter((k) => k.startsWith(prefix) && !k.slice(prefix.length).includes(\".\")).map((k) => k.slice(prefix.length))])] : keys;\n for (const key of all) {\n const path = prefix + key, value = record[key], range = ranges[path];\n if (value === undefined && !required) continue;\n if (range === undefined) fail(\"Unknown \" + (root + prefix).slice(0, -1) + \" field: \" + key + \".\");\n if (value === undefined) fail(root + path + \" is required.\");\n if (range === 0) {\n requireInputObject(value, root + path);\n checkFieldRanges(value as Record<string, unknown>, Object.keys(value as object), path + \".\", ranges, root, required);\n } else if (typeof range === \"function\") {\n if (!range(value)) fail(root + path + \" is invalid.\");\n } else {\n const choice = range.length > 2 || typeof range[0] !== \"number\", [minimum, maximum] = range as readonly [number, number];\n if (choice ? !(range as readonly unknown[]).includes(value) : typeof value !== \"number\" || !(value >= minimum && value <= maximum)) {\n fail(root + path + \" must be \" + (choice ? range.map(String).join(\" or \") : \"a number from \" + minimum + \" to \" + maximum) + \".\");\n }\n }\n }\n}\n\n/** Checks the characteristics of the request's `sensor` record (its other keys already checked); undefined when it sets none. */\nexport function parseSensorCharacteristics(sensor: Record<string, unknown>): ParsedSensorCharacteristics | undefined {\n if (!SENSOR_CHARACTERISTIC_KEYS.some((key) => sensor[key] !== undefined)) return undefined;\n checkFieldRanges(sensor, SENSOR_CHARACTERISTIC_KEYS, \"\");\n const c = sensor as SimulateCaptureSensorCharacteristics, fullWell = c.fullWell;\n if (fullWell && fullWell.electrons === undefined) fail(\"sensor.fullWell.electrons is required.\");\n if (fullWell?.link === \"none\" && fullWell.referencePixelPitchMicrometers !== undefined) fail(\"sensor.fullWell.referencePixelPitchMicrometers applies only to link \\\"pixel-area\\\".\");\n return { ...c, qeScale: (c.peakQuantumEfficiency ?? 0.5) / 0.5 };\n}\n\nconst noiseOf = (regime: SensorReadoutConversionRegime): number => Math.hypot(...regime.readNoiseComponents.map((c) => c.rmsElectrons.value));\n/** The body's dual-gain read noise: the base regime's, and the first high-conversion-gain regime's with its ISO (none: the low gain throughout). */\nexport function declaredReadNoise(profile: SensorReadoutConversionProfile): SimulateCaptureSensorReport[\"readNoise\"] {\n const regimes = profile.regimes, high = regimes.find((r) => r.exposureIndexGain?.conversionGainMode === \"high-conversion-gain\"), low = noiseOf(regimes[0]!);\n return { lowGainElectrons: low, highGainElectrons: high ? noiseOf(high) : low, highGainFromIso: high ? high.exposureIndexGain!.exposureIndex : null };\n}\n\n/**\n * The readout profile with the characteristics applied: every regime's charge-domain transfer (system conversion gain,\n * e⁻ per code, and pre-ADC saturation, e⁻ equivalent) multiplied by `chargeScale`, so a full well `chargeScale` times the\n * body's reaches the same white code; with `adcBitDepth`, the black level ×2^Δbits (rounded), the saturation code 2^bits − 1\n * and the conversion gain over the new code span; with `readNoise`, one electronic read-noise component per regime, the high\n * conversion gain's from `highGainFromIso`. Codes per unit charge of the base regime otherwise, digital gain and the ISO\n * selection are unchanged. Re-checked by the readout parser. With nothing to change, the profile itself.\n */\nexport function characterizeReadout(profile: SensorReadoutConversionProfile, chargeScale: number, c?: ParsedSensorCharacteristics): SensorReadoutConversionProfile {\n const bits = c?.adcBitDepth, n = c?.readNoise, declared = n && declaredReadNoise(profile);\n if (chargeScale === 1 && !bits && !n) return profile;\n return parseSensorReadoutConversionProfile({ ...profile, regimes: profile.regimes.map((regime) => {\n const adc = regime.adc, rescale = bits !== undefined && bits !== adc.bitDepth;\n const black = rescale ? Math.round(adc.blackLevelCode * 2 ** (bits - adc.bitDepth)) : adc.blackLevelCode, saturation = rescale ? 2 ** bits - 1 : adc.digitalSaturationCode;\n const out = { ...regime, systemConversionGainElectronsPerCode: { ...regime.systemConversionGainElectronsPerCode, value: rescale ?\n regime.systemConversionGainElectronsPerCode.value * chargeScale * (adc.digitalSaturationCode - adc.blackLevelCode) / (saturation - black) : regime.systemConversionGainElectronsPerCode.value * chargeScale },\n preAdcSaturationElectronEquivalent: { ...regime.preAdcSaturationElectronEquivalent, value: regime.preAdcSaturationElectronEquivalent.value * chargeScale },\n ...(rescale ? { adc: { ...adc, bitDepth: bits, blackLevelCode: black, digitalSaturationCode: saturation } } : {}) };\n if (n) {\n const gain = regime.exposureIndexGain ?? fail(\"sensor.readNoise needs readout regimes that declare their exposure index.\");\n const from = n.highGainFromIso ?? declared!.highGainFromIso ?? Infinity, high = gain.exposureIndex >= from;\n const value = high ? n.highGainElectrons ?? declared!.highGainElectrons : n.lowGainElectrons ?? declared!.lowGainElectrons;\n out.readNoiseComponents = [{ componentId: \"electronic\", rmsElectrons: { value, evidence: regime.evidence }, evidence: regime.evidence }];\n out.exposureIndexGain = { ...gain, conversionGainMode: high ? \"high-conversion-gain\" : \"low-conversion-gain\" };\n }\n return out;\n }) });\n}\n\n/** Charge at the RAW white level and the read-plus-quantization floor of one regime, e⁻. */\nfunction regimeRange(profile: SensorReadoutConversionProfile, regime: SensorReadoutConversionRegime, capacityElectrons: number): { saturation: number; quantization: number; read: number } {\n const adc = regime.adc, gain = regime.systemConversionGainElectronsPerCode.value, digitalGain = adc.digitalGain ?? 1;\n const white = calculateSiteEffectiveSaturationCode({ physicalChargeCapacityElectrons: capacityElectrons, preAdcSaturationElectronEquivalent: regime.preAdcSaturationElectronEquivalent.value,\n systemConversionGainElectronsPerCode: gain, blackLevelCode: adc.blackLevelCode, digitalSaturationCode: adc.digitalSaturationCode, adcMaximumCode: 2 ** adc.bitDepth - 1, digitalGain }, profile, regime.regimeId);\n // Output codes per electron d/G; quantization 1/12 code² of the ADC times d², plus 1/12 of the rounding after a digital gain.\n const perCode = gain / digitalGain;\n return { saturation: (white - adc.blackLevelCode) * perCode, quantization: perCode * Math.sqrt(digitalGain * digitalGain / 12 + (digitalGain === 1 ? 0 : 1 / 12)), read: noiseOf(regime) };\n}\n\n/**\n * The report: the characteristics used, the dynamic range per ISO, and the mid-grey noise figure for `greyElectrons`, the\n * green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge.\n */\nexport function sensorReport(profile: SensorReadoutConversionProfile, regimeId: string, capacityElectrons: number, greyElectrons: number | undefined, darkElectrons: number,\n base: Pick<SimulateCaptureSensorReport, \"pixelPitchMicrometers\" | \"peakQuantumEfficiency\" | \"readoutSeconds\">, pattern?: SensorFixedPatternNoiseProfile): SimulateCaptureSensorReport {\n const stops = (r: SensorReadoutConversionRegime): number => { const q = regimeRange(profile, r, capacityElectrons); return Math.log2(q.saturation / Math.hypot(q.read, q.quantization)); };\n const regime = profile.regimes.find((r) => r.regimeId === regimeId)!, range = regimeRange(profile, regime, capacityElectrons);\n const report: SimulateCaptureSensorReport = { ...base, fullWellElectrons: capacityElectrons, readNoise: declaredReadNoise(profile), adcBitDepth: regime.adc.bitDepth, fixedPatternNoise: !!pattern,\n dynamicRangeStops: stops(regime), dynamicRangeByIso: profile.regimes.map((r) => ({ iso: r.exposureIndexGain?.exposureIndex ?? NaN, stops: stops(r) })).filter((e) => e.iso > 0) };\n if (greyElectrons === undefined) return report;\n const fixedPattern = pattern && Math.hypot(pattern.photoResponseNonUniformity.relativeStandardDeviation.value * greyElectrons,\n pattern.darkSignalNonUniformity.relativeStandardDeviation.value * darkElectrons, pattern.rowOffset.rmsElectrons.value, pattern.columnOffset.rmsElectrons.value);\n const noise = { photonShot: Math.sqrt(greyElectrons), read: range.read, darkCurrent: Math.sqrt(darkElectrons), quantization: range.quantization, ...(fixedPattern === undefined ? {} : { fixedPattern }), total: 0 };\n noise.total = Math.hypot(noise.photonShot, noise.read, noise.darkCurrent, noise.quantization, fixedPattern ?? 0);\n const snr = greyElectrons / noise.total;\n report.midGrey = { reflectance: 0.18, channel: \"green\", signalElectrons: greyElectrons, noiseElectrons: noise, snr, snrDecibels: 20 * Math.log10(snr || Number.MIN_VALUE), clipped: greyElectrons + darkElectrons >= range.saturation };\n return report;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sensor-characteristics.js","sourceRoot":"","sources":["../../src/sensor/sensor-characteristics.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Adjustable sensor characteristics for `simulateCapture` (#555) and the capture's noise figure (#535): the request\n * options that replace a camera body's sensor values one at a time, the readout they give, and the calculated dynamic\n * range and mid-grey signal-to-noise ratio the result reports.\n *\n * Everything here works on the body's declared readout regimes, so it applies to a generic tier and to a caller's\n * preset record alike, and a later sensor profile (#554) can supply the same options as data.\n * @see docs/QUICK_START.md, \"Sensor characteristics (#555)\", for the options, the equations and their sources.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireInputObject } from \"../core/record-validation.js\";\nimport { calculateSiteEffectiveSaturationCode } from \"../capture/raw-white-level.js\";\nimport { parseSensorReadoutConversionProfile, type SensorReadoutConversionProfile, type SensorReadoutConversionRegime } from \"./raw-readout.js\";\nimport type { SensorFixedPatternNoiseProfile } from \"./fixed-pattern-noise.js\";\n\n/**\n * #555: sensor characteristics a capture can set one at a time (`simulateCapture({ sensor })`). Each is off by default: an\n * omitted field keeps the camera body's value, and a request with none of them is byte-identical to one before 2.20.0.\n * Every value set is listed in the result's `assumptions`.\n */\nexport interface SimulateCaptureSensorCharacteristics {\n /**\n * Peak external quantum efficiency of the green channel (its value at 555 nm), 0.05–0.9. Default 0.5, the facade's\n * idealized sensor. Every channel's EQE curve is multiplied by `peakQuantumEfficiency / 0.5`, so the curves keep their\n * shape (and the colour matrix is unchanged) and the collected charge scales in proportion.\n */\n peakQuantumEfficiency?: number;\n /**\n * Full-well capacity: the charge, e⁻, that reaches the RAW white level at base ISO. Default: the body's base-ISO saturation\n * at 6 µm, scaled by the pixel area (the `sensor.isoAreaNormalization` approximation). `link` `\"pixel-area\"` (default)\n * scales `electrons`, the full well at `referencePixelPitchMicrometers` (default 6), by (pitch / reference)²; `\"none\"`\n * uses `electrons` at any pitch. The result is 100 e⁻ to 1e10 e⁻.\n */\n fullWell?: { electrons: number; link?: \"pixel-area\" | \"none\"; referencePixelPitchMicrometers?: number };\n /**\n * Read noise, RMS e⁻ (0–100), of one physical photosite, as a sensor datasheet gives it (2.22.0: on a tier too; a rendered site that\n * sums N photosites, above the tier's 6 µm or a profile's own pitch, takes it ×√N), of the low and the high conversion gain, and\n * the dual-gain switch: the ISO setting from\n * which the high conversion gain is used (any ISO from 100; above the body's highest ISO, the low gain is used throughout).\n * An omitted field keeps the body's value. Needs readout regimes that declare their exposure index (the tiers do).\n */\n readNoise?: { lowGainElectrons?: number; highGainElectrons?: number; highGainFromIso?: number };\n /** Readout time, s (1e-6 to 1): the rolling shutter's top-to-bottom skew. Default: the body's (tiers 8, 15 and 30 ms). */\n readoutSeconds?: number;\n /**\n * ADC bit depth: 12, 14 or 16. Default: the body's (consumer 12, the others 14). The black level scales with the code range\n * (×2^Δbits), and the conversion gain follows so the same charge reaches the same fraction of the range.\n */\n adcBitDepth?: 12 | 14 | 16;\n /**\n * Opt-in (advanced): the body's synthetic fixed-pattern noise profile (PRNU, DSNU, hot pixels, row and column offsets; EMVA\n * 1288 Release 4.0 non-uniformities), the same pattern in every capture. Default false. Not in `simulateDisplayView`.\n */\n fixedPatternNoise?: boolean;\n}\n\n/** The option keys of {@link SimulateCaptureSensorCharacteristics}, in `sensor`. */\nexport const SENSOR_CHARACTERISTIC_KEYS = [\"peakQuantumEfficiency\", \"fullWell\", \"readNoise\", \"readoutSeconds\", \"adcBitDepth\", \"fixedPatternNoise\"] as const;\n\n/** One RMS noise component of the mid-grey figure, e⁻ (input-referred). */\nexport interface SimulateCaptureNoiseComponents {\n /** Photon shot noise √S (Poisson). */\n photonShot: number;\n /** Read noise of the capture's readout regime. */\n read: number;\n /** Dark-current shot noise √(D·t), D = 1 e⁻/s at 20 °C. */\n darkCurrent: number;\n /** ADC quantization, one code (and one more after a digital gain) as a uniform 1/12 code² variance, in e⁻. */\n quantization: number;\n /** With `sensor.fixedPatternNoise`: √((PRNU·S)² + (DSNU·D·t)² + row² + column²), hot pixels excluded; a spatial, not temporal, noise. */\n fixedPattern?: number;\n /** √ of the sum of the squares. */\n total: number;\n}\n\n/**\n * #555, #535: the capture's sensor characteristics as used, and the figures calculated from them (reported, not settable).\n * Model figures from the engine's own noise model, not measurements.\n */\nexport interface SimulateCaptureSensorReport {\n pixelPitchMicrometers: number;\n /** The green channel's peak EQE (at 555 nm). */\n peakQuantumEfficiency: number;\n /** Full-well capacity, e⁻: the base-ISO saturation charge (`sensor.capacityElectrons`). */\n fullWellElectrons: number;\n /** Read noise, RMS e⁻, of each conversion gain, and the ISO from which the high gain is used (null: one gain throughout). */\n readNoise: { lowGainElectrons: number; highGainElectrons: number; highGainFromIso: number | null };\n /** The body's rolling readout skew, s (null when it declares no rolling readout). */\n readoutSeconds: number | null;\n /** ADC bit depth of the capture's readout regime. */\n adcBitDepth: number;\n fixedPatternNoise: boolean;\n /**\n * Engineering dynamic range at the capture's ISO, stops: log₂(S_sat / σ_floor), with S_sat the charge at the RAW white\n * level (full well over the analog gain, limited by the charge capacity and the ADC range) and σ_floor the read noise and\n * ADC quantization added in quadrature (the temporal dark noise of EMVA 1288 Release 4.0 without dark current). An ideal\n * noiseless readout is limited by the ADC alone: log₂((white − black)·√12) for unit digital gain.\n */\n dynamicRangeStops: number;\n /** The same per ISO setting of the body, in its order. */\n dynamicRangeByIso: readonly { iso: number; stops: number }[];\n /**\n * #535: the expected signal-to-noise ratio of an 18 % Lambertian grey patch under the request's illuminant and illuminance\n * at the capture's exposure, ISO and sensor, on the green channel next to the optical axis (no vignetting, PSF or motion).\n * `snr` = S/σ_total and `snrDecibels` = 20·log₁₀(snr). `clipped`: S is at or above the white level, where the RAW clips and\n * the figure no longer describes the image. Computed only when the request resolves to exact (`fast: false`, the default for\n * DNG, JPEG and PNG exports; omitted for a fast request).\n */\n midGrey?: SimulateCaptureMidGreyFigure;\n /**\n * #595 (2.22.0): the same figures for one of the body's native photosites, whatever pitch the capture renders (the fields above\n * are the as-rendered site's). The native pitch is a generic tier's (or a caller preset's) declared 6 µm reference, or a sensor\n * profile's own photosite; the request's sensor characteristics apply as they would at that pitch. Exact arithmetic under the\n * engine's model (model figures, not measurements); see docs/QUICK_START.md, \"Native-pixel dynamic range (#595)\".\n */\n native: SimulateCaptureNativeSensorReport;\n}\n\n/** #535: the mid-grey noise figure of {@link SimulateCaptureSensorReport.midGrey}. */\nexport interface SimulateCaptureMidGreyFigure { reflectance: 0.18; channel: \"green\"; signalElectrons: number; noiseElectrons: SimulateCaptureNoiseComponents; snr: number; snrDecibels: number; clipped: boolean }\n\n/** #595: {@link SimulateCaptureSensorReport.native}, the figures of one native photosite. */\nexport interface SimulateCaptureNativeSensorReport {\n /** The native photosite pitch, µm (tiers: 6; a profile: its own). */\n pixelPitchMicrometers: number;\n /** N = (rendered pitch / native pitch)²: the photosites one rendered site stands for (below 1, a site smaller than a photosite). */\n photositesPerSite: number;\n /** Full-well capacity of one photosite, e⁻. */\n fullWellElectrons: number;\n /** Read noise of one photosite at the capture's ISO, RMS e⁻ (the capture's readout regime). */\n readNoiseElectrons: number;\n /** Engineering dynamic range of one photosite at the capture's ISO, stops (the definition of `dynamicRangeStops`). */\n dynamicRangeStops: number;\n /** The same per ISO setting of the body. */\n dynamicRangeByIso: readonly { iso: number; stops: number }[];\n /**\n * EMVA 1288 Release 4.0 (§2.7, Eq. 26–28) at the capture's ISO: the absolute sensitivity threshold μ_e.min = √(σ_d² + σ_q² + 1/4) + 1/2\n * (σ_d the read noise, dark current excluded; σ_q the quantization noise in e⁻), DR = μ_e.sat / μ_e.min in stops and dB, and the\n * saturation capacity per area μ_e.sat / p² (Table 2).\n */\n emva1288: { absoluteSensitivityThresholdElectrons: number; dynamicRangeStops: number; dynamicRangeDecibels: number; saturationCapacityElectronsPerSquareMicrometer: number };\n /**\n * A convention, not part of EMVA 1288: the dynamic range of the whole frame's native photosites resampled to 8 million output\n * pixels, each summing framePhotosites / 8e6 photosites with their read noise (EMVA 1288 R4.0 §6.4, binning):\n * DR + ½·log₂(framePhotosites / 8e6), for the engineering and the EMVA figure. No ADC limit is applied to the sum.\n */\n normalized8Megapixel: { referencePixels: 8_000_000; framePhotosites: number; dynamicRangeStops: number; emva1288DynamicRangeStops: number };\n /** The mid-grey figure for one photosite (its charge (native/rendered pitch)² of the site's), present when `midGrey` is. */\n midGrey?: SimulateCaptureMidGreyFigure;\n}\n\n/** #595: what {@link sensorReport} needs for the native figures: the readout as characterized at the native pitch. */\nexport interface NativeSensorInput { profile: SensorReadoutConversionProfile; capacityElectrons: number; pixelPitchMicrometers: number; framePhotosites: number }\n\n/** The request's characteristics, range-checked, with the EQE scale peakQuantumEfficiency / 0.5. */\nexport type ParsedSensorCharacteristics = SimulateCaptureSensorCharacteristics & { qeScale: number };\n\n/**\n * A field's check: [minimum, maximum] (a number), the values allowed, 0 (a nested record) or a predicate. #554 shares the checker\n * with the sensor profile parser.\n */\nexport type FieldRange = readonly [number, number] | readonly unknown[] | 0 | ((value: unknown) => boolean);\n/** Accepted ranges (design limits) and values of each characteristic, by path below `sensor`. */\nconst RANGES: Readonly<Record<string, FieldRange>> = { peakQuantumEfficiency: [0.05, 0.9], fullWell: 0, \"fullWell.electrons\": [100, 1e10],\n \"fullWell.link\": [\"pixel-area\", \"none\"], \"fullWell.referencePixelPitchMicrometers\": [0.5, 1000], readNoise: 0, \"readNoise.lowGainElectrons\": [0, 100],\n \"readNoise.highGainElectrons\": [0, 100], \"readNoise.highGainFromIso\": [100, 1e7], readoutSeconds: [1e-6, 1], adcBitDepth: [12, 14, 16], fixedPatternNoise: [true, false] };\nfunction fail(message: string): never { throw new InvalidConfigurationError(message); }\n/**\n * Checks `keys` of `record` against `ranges` (by path below `root`; nested records by \"a.b\"). An undefined field is skipped, or\n * with `required` refused, and with `required` every field `ranges` lists at that level is checked.\n */\nexport function checkFieldRanges(record: Record<string, unknown>, keys: readonly string[], prefix: string, ranges: Readonly<Record<string, FieldRange>> = RANGES, root = \"sensor.\", required = false): void {\n const all = required ? [...new Set([...keys, ...Object.keys(ranges).filter((k) => k.startsWith(prefix) && !k.slice(prefix.length).includes(\".\")).map((k) => k.slice(prefix.length))])] : keys;\n for (const key of all) {\n const path = prefix + key, value = record[key], range = ranges[path];\n if (value === undefined && !required) continue;\n if (range === undefined) fail(\"Unknown \" + (root + prefix).slice(0, -1) + \" field: \" + key + \".\");\n if (value === undefined) fail(root + path + \" is required.\");\n if (range === 0) {\n requireInputObject(value, root + path);\n checkFieldRanges(value as Record<string, unknown>, Object.keys(value as object), path + \".\", ranges, root, required);\n } else if (typeof range === \"function\") {\n if (!range(value)) fail(root + path + \" is invalid.\");\n } else {\n const choice = range.length > 2 || typeof range[0] !== \"number\", [minimum, maximum] = range as readonly [number, number];\n if (choice ? !(range as readonly unknown[]).includes(value) : typeof value !== \"number\" || !(value >= minimum && value <= maximum)) {\n fail(root + path + \" must be \" + (choice ? range.map(String).join(\" or \") : \"a number from \" + minimum + \" to \" + maximum) + \".\");\n }\n }\n }\n}\n\n/** Checks the characteristics of the request's `sensor` record (its other keys already checked); undefined when it sets none. */\nexport function parseSensorCharacteristics(sensor: Record<string, unknown>): ParsedSensorCharacteristics | undefined {\n if (!SENSOR_CHARACTERISTIC_KEYS.some((key) => sensor[key] !== undefined)) return undefined;\n checkFieldRanges(sensor, SENSOR_CHARACTERISTIC_KEYS, \"\");\n const c = sensor as SimulateCaptureSensorCharacteristics, fullWell = c.fullWell;\n if (fullWell && fullWell.electrons === undefined) fail(\"sensor.fullWell.electrons is required.\");\n if (fullWell?.link === \"none\" && fullWell.referencePixelPitchMicrometers !== undefined) fail(\"sensor.fullWell.referencePixelPitchMicrometers applies only to link \\\"pixel-area\\\".\");\n return { ...c, qeScale: (c.peakQuantumEfficiency ?? 0.5) / 0.5 };\n}\n\nconst noiseOf = (regime: SensorReadoutConversionRegime): number => Math.hypot(...regime.readNoiseComponents.map((c) => c.rmsElectrons.value));\n/** The body's dual-gain read noise: the base regime's, and the first high-conversion-gain regime's with its ISO (none: the low gain throughout). */\nexport function declaredReadNoise(profile: SensorReadoutConversionProfile): SimulateCaptureSensorReport[\"readNoise\"] {\n const regimes = profile.regimes, high = regimes.find((r) => r.exposureIndexGain?.conversionGainMode === \"high-conversion-gain\"), low = noiseOf(regimes[0]!);\n return { lowGainElectrons: low, highGainElectrons: high ? noiseOf(high) : low, highGainFromIso: high ? high.exposureIndexGain!.exposureIndex : null };\n}\n\n/**\n * The readout profile with the characteristics applied: every regime's charge-domain transfer (system conversion gain,\n * e⁻ per code, and pre-ADC saturation, e⁻ equivalent) multiplied by `chargeScale`, so a full well `chargeScale` times the\n * body's reaches the same white code; with `adcBitDepth`, the black level ×2^Δbits (rounded), the saturation code 2^bits − 1\n * and the conversion gain over the new code span; with `readNoise`, one electronic read-noise component per regime, the high\n * conversion gain's from `highGainFromIso`; every read noise (the body's or the request's) times `readNoiseScale` (#599: √N for a site\n * that sums N photosites, as the sensor-profile path does). Codes per unit charge of the base regime otherwise, digital gain and the ISO\n * selection are unchanged. Re-checked by the readout parser. With nothing to change, the profile itself.\n */\nexport function characterizeReadout(profile: SensorReadoutConversionProfile, chargeScale: number, c?: ParsedSensorCharacteristics, readNoiseScale = 1): SensorReadoutConversionProfile {\n const bits = c?.adcBitDepth, n = c?.readNoise, declared = n && declaredReadNoise(profile);\n if (chargeScale === 1 && !bits && !n && readNoiseScale === 1) return profile;\n return parseSensorReadoutConversionProfile({ ...profile, regimes: profile.regimes.map((regime) => {\n const adc = regime.adc, rescale = bits !== undefined && bits !== adc.bitDepth;\n const black = rescale ? Math.round(adc.blackLevelCode * 2 ** (bits - adc.bitDepth)) : adc.blackLevelCode, saturation = rescale ? 2 ** bits - 1 : adc.digitalSaturationCode;\n const out = { ...regime, systemConversionGainElectronsPerCode: { ...regime.systemConversionGainElectronsPerCode, value: rescale ?\n regime.systemConversionGainElectronsPerCode.value * chargeScale * (adc.digitalSaturationCode - adc.blackLevelCode) / (saturation - black) : regime.systemConversionGainElectronsPerCode.value * chargeScale },\n preAdcSaturationElectronEquivalent: { ...regime.preAdcSaturationElectronEquivalent, value: regime.preAdcSaturationElectronEquivalent.value * chargeScale },\n ...(rescale ? { adc: { ...adc, bitDepth: bits, blackLevelCode: black, digitalSaturationCode: saturation } } : {}) };\n if (n) {\n const gain = regime.exposureIndexGain ?? fail(\"sensor.readNoise needs readout regimes that declare their exposure index.\");\n const from = n.highGainFromIso ?? declared!.highGainFromIso ?? Infinity, high = gain.exposureIndex >= from;\n const value = (high ? n.highGainElectrons ?? declared!.highGainElectrons : n.lowGainElectrons ?? declared!.lowGainElectrons) * readNoiseScale;\n out.readNoiseComponents = [{ componentId: \"electronic\", rmsElectrons: { value, evidence: regime.evidence }, evidence: regime.evidence }];\n out.exposureIndexGain = { ...gain, conversionGainMode: high ? \"high-conversion-gain\" : \"low-conversion-gain\" };\n } else if (readNoiseScale !== 1) out.readNoiseComponents = regime.readNoiseComponents.map((component) => ({ ...component, rmsElectrons: { ...component.rmsElectrons, value: component.rmsElectrons.value * readNoiseScale } }));\n return out;\n }) });\n}\n\n/** Charge at the RAW white level and the read-plus-quantization floor of one regime, e⁻. */\nexport function regimeRange(profile: SensorReadoutConversionProfile, regime: SensorReadoutConversionRegime, capacityElectrons: number): { saturation: number; quantization: number; read: number } {\n const adc = regime.adc, gain = regime.systemConversionGainElectronsPerCode.value, digitalGain = adc.digitalGain ?? 1;\n const white = calculateSiteEffectiveSaturationCode({ physicalChargeCapacityElectrons: capacityElectrons, preAdcSaturationElectronEquivalent: regime.preAdcSaturationElectronEquivalent.value,\n systemConversionGainElectronsPerCode: gain, blackLevelCode: adc.blackLevelCode, digitalSaturationCode: adc.digitalSaturationCode, adcMaximumCode: 2 ** adc.bitDepth - 1, digitalGain }, profile, regime.regimeId);\n // Output codes per electron d/G; quantization 1/12 code² of the ADC times d², plus 1/12 of the rounding after a digital gain.\n const perCode = gain / digitalGain;\n return { saturation: (white - adc.blackLevelCode) * perCode, quantization: perCode * Math.sqrt(digitalGain * digitalGain / 12 + (digitalGain === 1 ? 0 : 1 / 12)), read: noiseOf(regime) };\n}\n\n/**\n * The report: the characteristics used, the dynamic range per ISO, and the mid-grey noise figure for `greyElectrons`, the\n * green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge. `native` (#595) is added by\n * `nativeSensorReport` (native-sensor-report.ts).\n */\nexport function sensorReport(profile: SensorReadoutConversionProfile, regimeId: string, capacityElectrons: number, greyElectrons: number | undefined, darkElectrons: number,\n base: Pick<SimulateCaptureSensorReport, \"pixelPitchMicrometers\" | \"peakQuantumEfficiency\" | \"readoutSeconds\">, pattern?: SensorFixedPatternNoiseProfile): SimulateCaptureSensorReport {\n const stops = (r: SensorReadoutConversionRegime): number => { const q = regimeRange(profile, r, capacityElectrons); return Math.log2(q.saturation / Math.hypot(q.read, q.quantization)); };\n const regime = profile.regimes.find((r) => r.regimeId === regimeId)!, range = regimeRange(profile, regime, capacityElectrons);\n const report = { ...base, fullWellElectrons: capacityElectrons, readNoise: declaredReadNoise(profile), adcBitDepth: regime.adc.bitDepth, fixedPatternNoise: !!pattern,\n dynamicRangeStops: stops(regime), dynamicRangeByIso: profile.regimes.map((r) => ({ iso: r.exposureIndexGain?.exposureIndex ?? NaN, stops: stops(r) })).filter((e) => e.iso > 0) } as unknown as SimulateCaptureSensorReport;\n if (greyElectrons === undefined) return report;\n const fixedPattern = pattern && Math.hypot(pattern.photoResponseNonUniformity.relativeStandardDeviation.value * greyElectrons,\n pattern.darkSignalNonUniformity.relativeStandardDeviation.value * darkElectrons, pattern.rowOffset.rmsElectrons.value, pattern.columnOffset.rmsElectrons.value);\n const noise = { photonShot: Math.sqrt(greyElectrons), read: range.read, darkCurrent: Math.sqrt(darkElectrons), quantization: range.quantization, ...(fixedPattern === undefined ? {} : { fixedPattern }), total: 0 };\n noise.total = Math.hypot(noise.photonShot, noise.read, noise.darkCurrent, noise.quantization, fixedPattern ?? 0);\n const snr = greyElectrons / noise.total;\n report.midGrey = { reflectance: 0.18, channel: \"green\", signalElectrons: greyElectrons, noiseElectrons: noise, snr, snrDecibels: 20 * Math.log10(snr || Number.MIN_VALUE), clipped: greyElectrons + darkElectrons >= range.saturation };\n return report;\n}\n"]}
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Public API reference
|
|
2
2
|
|
|
3
|
-
Generated from the exact root exports for **@photivra/engine 2.
|
|
3
|
+
Generated from the exact root exports for **@photivra/engine 2.22.0 / root contract 2.22.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
|
|
4
4
|
|
|
5
5
|
Use [the developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md) for executable paths and scientific boundaries. Types are contracts, not runtime validation: parse untrusted data, preserve units/reference frames, read assessment blockers, and retain evidence/limitations. Optional properties do not imply a universal default. Model/schema IDs remain independent of the distribution.
|
|
6
6
|
|
|
@@ -101,6 +101,28 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
101
101
|
| [`SensorEquivalenceFieldOfView`](api/camera-sensor-equivalence.md#sensorequivalencefieldofview) | Type |
|
|
102
102
|
| [`SensorEquivalenceQuantity`](api/camera-sensor-equivalence.md#sensorequivalencequantity) | Type |
|
|
103
103
|
|
|
104
|
+
## capture/c2pa-embed.ts
|
|
105
|
+
|
|
106
|
+
[Detailed contracts](api/capture-c2pa-embed.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/c2pa-embed.ts)
|
|
107
|
+
|
|
108
|
+
| Export | Kind |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| [`bindC2paManifestStore`](api/capture-c2pa-embed.md#bindc2pamanifeststore) | Runtime |
|
|
111
|
+
| [`BindC2paManifestStoreInput`](api/capture-c2pa-embed.md#bindc2pamanifeststoreinput) | Type |
|
|
112
|
+
| [`C2PA_ALGORITHMIC_MEDIA_SOURCE_TYPE`](api/capture-c2pa-embed.md#c2pa_algorithmic_media_source_type) | Runtime |
|
|
113
|
+
| [`C2PA_MANIFEST_STORE_MAXIMUM_BYTES`](api/capture-c2pa-embed.md#c2pa_manifest_store_maximum_bytes) | Runtime |
|
|
114
|
+
| [`C2paBoundFile`](api/capture-c2pa-embed.md#c2paboundfile) | Type |
|
|
115
|
+
| [`C2paByteRange`](api/capture-c2pa-embed.md#c2pabyterange) | Type |
|
|
116
|
+
| [`C2paDataHash`](api/capture-c2pa-embed.md#c2padatahash) | Type |
|
|
117
|
+
| [`C2paFoundManifestStore`](api/capture-c2pa-embed.md#c2pafoundmanifeststore) | Type |
|
|
118
|
+
| [`C2paHardBinding`](api/capture-c2pa-embed.md#c2pahardbinding) | Type |
|
|
119
|
+
| [`calculateC2paDataHash`](api/capture-c2pa-embed.md#calculatec2padatahash) | Runtime |
|
|
120
|
+
| [`CalculateC2paDataHashInput`](api/capture-c2pa-embed.md#calculatec2padatahashinput) | Type |
|
|
121
|
+
| [`parseC2paManifestStore`](api/capture-c2pa-embed.md#parsec2pamanifeststore) | Runtime |
|
|
122
|
+
| [`ParseC2paManifestStoreInput`](api/capture-c2pa-embed.md#parsec2pamanifeststoreinput) | Type |
|
|
123
|
+
| [`setC2paManifestStore`](api/capture-c2pa-embed.md#setc2pamanifeststore) | Runtime |
|
|
124
|
+
| [`SetC2paManifestStoreInput`](api/capture-c2pa-embed.md#setc2pamanifeststoreinput) | Type |
|
|
125
|
+
|
|
104
126
|
## capture/capture-export-metadata.ts
|
|
105
127
|
|
|
106
128
|
[Detailed contracts](api/capture-capture-export-metadata.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/capture-export-metadata.ts)
|
|
@@ -124,13 +146,57 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
124
146
|
| --- | --- |
|
|
125
147
|
| [`DNG_IMPORT_LIMITS`](api/capture-dng-import.md#dng_import_limits) | Runtime |
|
|
126
148
|
| [`DNG_IMPORT_SCHEMA_VERSION`](api/capture-dng-import.md#dng_import_schema_version) | Runtime |
|
|
149
|
+
| [`DngImportAsyncInput`](api/capture-dng-import.md#dngimportasyncinput) | Type |
|
|
127
150
|
| [`DngImportInput`](api/capture-dng-import.md#dngimportinput) | Type |
|
|
151
|
+
| [`importDngRawImage`](api/capture-dng-import.md#importdngrawimage) | Runtime |
|
|
128
152
|
| [`ImportedDngBlackLevel`](api/capture-dng-import.md#importeddngblacklevel) | Type |
|
|
129
153
|
| [`ImportedDngCfa`](api/capture-dng-import.md#importeddngcfa) | Type |
|
|
130
154
|
| [`ImportedDngRaw`](api/capture-dng-import.md#importeddngraw) | Type |
|
|
131
155
|
| [`ImportedDngRect`](api/capture-dng-import.md#importeddngrect) | Type |
|
|
156
|
+
| [`ImportedDngSemanticMask`](api/capture-dng-import.md#importeddngsemanticmask) | Type |
|
|
132
157
|
| [`parseDngRawImage`](api/capture-dng-import.md#parsedngrawimage) | Runtime |
|
|
133
158
|
|
|
159
|
+
## capture/dng-opcodes.ts
|
|
160
|
+
|
|
161
|
+
[Detailed contracts](api/capture-dng-opcodes.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/dng-opcodes.ts)
|
|
162
|
+
|
|
163
|
+
| Export | Kind |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| [`applyDngOpcodes`](api/capture-dng-opcodes.md#applydngopcodes) | Runtime |
|
|
166
|
+
| [`ApplyDngOpcodesOptions`](api/capture-dng-opcodes.md#applydngopcodesoptions) | Type |
|
|
167
|
+
| [`DngOpcode`](api/capture-dng-opcodes.md#dngopcode) | Type |
|
|
168
|
+
| [`DngOpcodeArea`](api/capture-dng-opcodes.md#dngopcodearea) | Type |
|
|
169
|
+
| [`DngOpcodeParameters`](api/capture-dng-opcodes.md#dngopcodeparameters) | Type |
|
|
170
|
+
| [`DngStageImage`](api/capture-dng-opcodes.md#dngstageimage) | Type |
|
|
171
|
+
| [`parseDngOpcodeList`](api/capture-dng-opcodes.md#parsedngopcodelist) | Runtime |
|
|
172
|
+
|
|
173
|
+
## capture/dng-profile-maps.ts
|
|
174
|
+
|
|
175
|
+
[Detailed contracts](api/capture-dng-profile-maps.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/dng-profile-maps.ts)
|
|
176
|
+
|
|
177
|
+
| Export | Kind |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| [`applyDngProfileGainTableMap`](api/capture-dng-profile-maps.md#applydngprofilegaintablemap) | Runtime |
|
|
180
|
+
| [`ApplyDngProfileGainTableMapInput`](api/capture-dng-profile-maps.md#applydngprofilegaintablemapinput) | Type |
|
|
181
|
+
| [`applyDngRgbTables`](api/capture-dng-profile-maps.md#applydngrgbtables) | Runtime |
|
|
182
|
+
| [`ApplyDngRgbTablesInput`](api/capture-dng-profile-maps.md#applydngrgbtablesinput) | Type |
|
|
183
|
+
| [`DngProfileGainTableMap`](api/capture-dng-profile-maps.md#dngprofilegaintablemap) | Type |
|
|
184
|
+
| [`DngRgbImage`](api/capture-dng-profile-maps.md#dngrgbimage) | Type |
|
|
185
|
+
| [`DngRgbTable`](api/capture-dng-profile-maps.md#dngrgbtable) | Type |
|
|
186
|
+
| [`DngRgbTables`](api/capture-dng-profile-maps.md#dngrgbtables) | Type |
|
|
187
|
+
| [`parseDngProfileGainTableMap`](api/capture-dng-profile-maps.md#parsedngprofilegaintablemap) | Runtime |
|
|
188
|
+
| [`parseDngRgbTables`](api/capture-dng-profile-maps.md#parsedngrgbtables) | Runtime |
|
|
189
|
+
|
|
190
|
+
## capture/dng-stage-image.ts
|
|
191
|
+
|
|
192
|
+
[Detailed contracts](api/capture-dng-stage-image.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/dng-stage-image.ts)
|
|
193
|
+
|
|
194
|
+
| Export | Kind |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| [`developDngStageImage`](api/capture-dng-stage-image.md#developdngstageimage) | Runtime |
|
|
197
|
+
| [`DevelopDngStageImageOptions`](api/capture-dng-stage-image.md#developdngstageimageoptions) | Type |
|
|
198
|
+
| [`DngStageImageResult`](api/capture-dng-stage-image.md#dngstageimageresult) | Type |
|
|
199
|
+
|
|
134
200
|
## capture/environment-raw-producer.ts
|
|
135
201
|
|
|
136
202
|
[Detailed contracts](api/capture-environment-raw-producer.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/environment-raw-producer.ts)
|
|
@@ -180,6 +246,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
180
246
|
| [`setManualFocusState`](api/capture-focus-control.md#setmanualfocusstate) | Runtime |
|
|
181
247
|
| [`SetManualFocusStateInput`](api/capture-focus-control.md#setmanualfocusstateinput) | Type |
|
|
182
248
|
|
|
249
|
+
## capture/jpeg-xl-tiles.ts
|
|
250
|
+
|
|
251
|
+
[Detailed contracts](api/capture-jpeg-xl-tiles.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/jpeg-xl-tiles.ts)
|
|
252
|
+
|
|
253
|
+
| Export | Kind |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| [`createJxlOxideDngDecoder`](api/capture-jpeg-xl-tiles.md#createjxloxidedngdecoder) | Runtime |
|
|
256
|
+
| [`DngJpegXlDecoder`](api/capture-jpeg-xl-tiles.md#dngjpegxldecoder) | Type |
|
|
257
|
+
| [`JxlOxideModule`](api/capture-jpeg-xl-tiles.md#jxloxidemodule) | Type |
|
|
258
|
+
|
|
183
259
|
## capture/linear-encoding.ts
|
|
184
260
|
|
|
185
261
|
[Detailed contracts](api/capture-linear-encoding.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/linear-encoding.ts)
|
|
@@ -204,6 +280,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
204
280
|
| [`LosslessJpegLimits`](api/capture-lossless-jpeg.md#losslessjpeglimits) | Type |
|
|
205
281
|
| [`parseLosslessJpeg`](api/capture-lossless-jpeg.md#parselosslessjpeg) | Runtime |
|
|
206
282
|
|
|
283
|
+
## capture/lossy-jpeg.ts
|
|
284
|
+
|
|
285
|
+
[Detailed contracts](api/capture-lossy-jpeg.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/lossy-jpeg.ts)
|
|
286
|
+
|
|
287
|
+
| Export | Kind |
|
|
288
|
+
| --- | --- |
|
|
289
|
+
| [`LossyJpegImage`](api/capture-lossy-jpeg.md#lossyjpegimage) | Type |
|
|
290
|
+
| [`LossyJpegLimits`](api/capture-lossy-jpeg.md#lossyjpeglimits) | Type |
|
|
291
|
+
| [`parseLossyJpeg`](api/capture-lossy-jpeg.md#parselossyjpeg) | Runtime |
|
|
292
|
+
|
|
207
293
|
## capture/native-environment-optical-stage.ts
|
|
208
294
|
|
|
209
295
|
[Detailed contracts](api/capture-native-environment-optical-stage.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/capture/native-environment-optical-stage.ts)
|
|
@@ -727,6 +813,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
727
813
|
| --- | --- |
|
|
728
814
|
| [`InvalidConfigurationError`](api/core-configuration-error.md#invalidconfigurationerror) | Runtime |
|
|
729
815
|
|
|
816
|
+
## core/execution-error.ts
|
|
817
|
+
|
|
818
|
+
[Detailed contracts](api/core-execution-error.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/core/execution-error.ts)
|
|
819
|
+
|
|
820
|
+
| Export | Kind |
|
|
821
|
+
| --- | --- |
|
|
822
|
+
| [`ImageKernelExecutionError`](api/core-execution-error.md#imagekernelexecutionerror) | Runtime |
|
|
823
|
+
| [`ImageKernelFailureReason`](api/core-execution-error.md#imagekernelfailurereason) | Type |
|
|
824
|
+
| [`ImageKernelFallbackReason`](api/core-execution-error.md#imagekernelfallbackreason) | Type |
|
|
825
|
+
|
|
730
826
|
## core/image-formation.ts
|
|
731
827
|
|
|
732
828
|
[Detailed contracts](api/core-image-formation.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/core/image-formation.ts)
|
|
@@ -1376,6 +1472,14 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1376
1472
|
| [`MixedIlluminationPointSource`](api/facade-mixed-illumination.md#mixedilluminationpointsource) | Type |
|
|
1377
1473
|
| [`MixedIlluminationSource`](api/facade-mixed-illumination.md#mixedilluminationsource) | Type |
|
|
1378
1474
|
|
|
1475
|
+
## facade/quality-paths.ts
|
|
1476
|
+
|
|
1477
|
+
[Detailed contracts](api/facade-quality-paths.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/quality-paths.ts)
|
|
1478
|
+
|
|
1479
|
+
| Export | Kind |
|
|
1480
|
+
| --- | --- |
|
|
1481
|
+
| [`prepareQualityPaths`](api/facade-quality-paths.md#preparequalitypaths) | Runtime |
|
|
1482
|
+
|
|
1379
1483
|
## facade/representative-light-sources.ts
|
|
1380
1484
|
|
|
1381
1485
|
[Detailed contracts](api/facade-representative-light-sources.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/representative-light-sources.ts)
|
|
@@ -1524,9 +1628,6 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1524
1628
|
| [`ImageKernelBackend`](api/gpu-image-kernel-execution.md#imagekernelbackend) | Type |
|
|
1525
1629
|
| [`ImageKernelCodeErrorBound`](api/gpu-image-kernel-execution.md#imagekernelcodeerrorbound) | Type |
|
|
1526
1630
|
| [`ImageKernelExecution`](api/gpu-image-kernel-execution.md#imagekernelexecution) | Type |
|
|
1527
|
-
| [`ImageKernelExecutionError`](api/gpu-image-kernel-execution.md#imagekernelexecutionerror) | Runtime |
|
|
1528
|
-
| [`ImageKernelFailureReason`](api/gpu-image-kernel-execution.md#imagekernelfailurereason) | Type |
|
|
1529
|
-
| [`ImageKernelFallbackReason`](api/gpu-image-kernel-execution.md#imagekernelfallbackreason) | Type |
|
|
1530
1631
|
| [`ImageKernelRelativeErrorBound`](api/gpu-image-kernel-execution.md#imagekernelrelativeerrorbound) | Type |
|
|
1531
1632
|
| [`ImageKernelSeededRealizationBound`](api/gpu-image-kernel-execution.md#imagekernelseededrealizationbound) | Type |
|
|
1532
1633
|
| [`WEBGPU_F32_TRANSFER_ABSOLUTE_BOUND`](api/gpu-image-kernel-execution.md#webgpu_f32_transfer_absolute_bound) | Runtime |
|
|
@@ -2189,6 +2290,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
2189
2290
|
| [`ZernikeCoefficient`](api/optics-zernike-wavefront.md#zernikecoefficient) | Type |
|
|
2190
2291
|
| [`ZernikeOrder`](api/optics-zernike-wavefront.md#zernikeorder) | Type |
|
|
2191
2292
|
|
|
2293
|
+
## output/camera-channel-response.ts
|
|
2294
|
+
|
|
2295
|
+
[Detailed contracts](api/output-camera-channel-response.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/camera-channel-response.ts)
|
|
2296
|
+
|
|
2297
|
+
| Export | Kind |
|
|
2298
|
+
| --- | --- |
|
|
2299
|
+
| [`calculateCameraChannelResponse`](api/output-camera-channel-response.md#calculatecamerachannelresponse) | Runtime |
|
|
2300
|
+
| [`CalculateCameraChannelResponseInput`](api/output-camera-channel-response.md#calculatecamerachannelresponseinput) | Type |
|
|
2301
|
+
| [`CameraChannelResponse`](api/output-camera-channel-response.md#camerachannelresponse) | Type |
|
|
2302
|
+
|
|
2192
2303
|
## output/capture-corrected-sdr.ts
|
|
2193
2304
|
|
|
2194
2305
|
[Detailed contracts](api/output-capture-corrected-sdr.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/capture-corrected-sdr.ts)
|
|
@@ -3291,6 +3402,8 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
3291
3402
|
|
|
3292
3403
|
| Export | Kind |
|
|
3293
3404
|
| --- | --- |
|
|
3405
|
+
| [`SimulateCaptureMidGreyFigure`](api/sensor-sensor-characteristics.md#simulatecapturemidgreyfigure) | Type |
|
|
3406
|
+
| [`SimulateCaptureNativeSensorReport`](api/sensor-sensor-characteristics.md#simulatecapturenativesensorreport) | Type |
|
|
3294
3407
|
| [`SimulateCaptureNoiseComponents`](api/sensor-sensor-characteristics.md#simulatecapturenoisecomponents) | Type |
|
|
3295
3408
|
| [`SimulateCaptureSensorCharacteristics`](api/sensor-sensor-characteristics.md#simulatecapturesensorcharacteristics) | Type |
|
|
3296
3409
|
| [`SimulateCaptureSensorReport`](api/sensor-sensor-characteristics.md#simulatecapturesensorreport) | Type |
|
package/docs/QUICK_START.md
CHANGED
|
@@ -48,7 +48,7 @@ the example above is [simulate-capture.mjs](examples/simulate-capture.mjs).
|
|
|
48
48
|
| `raw` | The packed native RAW: codes, black levels, saturation flags and per-phase white levels |
|
|
49
49
|
| `plan` | The native RAW plan the engine executed |
|
|
50
50
|
| `request` | The complete underlying request: `environmentRaw` (the native environment RAW input), `siteInput(index)` (rebuilds the physical input of any sensor site), `development` (the RAW development input) and `whiteBalanceState` |
|
|
51
|
-
| `sensor` | 2.20 (#555, #535): the sensor characteristics used (pitch, peak QE, full well, read noise of each conversion gain and the dual-gain switch ISO, readout time, ADC bit depth, fixed-pattern noise), the calculated dynamic range (`dynamicRangeStops`, and per ISO setting) and, on exact requests, the mid-gray noise figure (`midGrey`); see [Sensor characteristics](#sensor-characteristics-555) |
|
|
51
|
+
| `sensor` | 2.20 (#555, #535): the sensor characteristics used (pitch, peak QE, full well, read noise of each conversion gain and the dual-gain switch ISO, readout time, ADC bit depth, fixed-pattern noise), the calculated dynamic range (`dynamicRangeStops`, and per ISO setting) and, on exact requests, the mid-gray noise figure (`midGrey`); 2.22 (#595): the same for one native photosite with the EMVA 1288 and 8 MP-normalized dynamic range (`native`); see [Sensor characteristics](#sensor-characteristics-555) |
|
|
52
52
|
| `exposure` | Mode, resolved settings, EV100, `isoControl` (`"manual"` or `"automatic"`) and, with `iso: "auto"`, `autoIso` (how the ISO was chosen and what limited it), metered scene luminance, the deviation from the standard exposure in stops (`standardExposureDeviationStops`), the illuminance at which the settings give the standard exposure (`standardExposureIlluminanceLux`), from 2.14 the deviation from the standard exposure of an 18 % grey card under the scene's illuminance (`greyCardDeviationStops`, incident metering) and the exposure-mode resolver's full output |
|
|
53
53
|
| `scene` | Chart parameters, illuminant, distance, framed size and the scene's SHA-256 digest (it covers caller illuminant and reflectance spectra; see `sourceSha256`) |
|
|
54
54
|
| `diagnostics` | Radiance query count, highlight and rendering counters, per-stage timings (`timingsMs`) and, from 2.1, the opt-ins that ran (`options`); see [Cost of each option](#cost-of-each-option) |
|
|
@@ -110,7 +110,7 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
|
|
|
110
110
|
| `output.whiteBalance` | `"scene-illuminant"` | Also `"auto"`, `"none"`, `{ kelvin, tint? }` and `{ channelGains }`. |
|
|
111
111
|
| `output.jpegQuantizationStep` | 2 | JPEG only: constant step 1–255, not a manufacturer quality scale. |
|
|
112
112
|
| `sensor.pixelPitchMicrometers` | 6 | 0.5–1000 (2.5; 0.5–100 before). The sensor is `width × pitch` wide (see [Geometry](#geometry)); the ISO readout scales with the pixel area (see [ISO and pixel size](#iso-and-pixel-size)). Above 100 µm the capture is a downsized view of a whole frame (see [Whole-frame views](#whole-frame-views-25)). |
|
|
113
|
-
| `sensor.spectralStepNanometers` | 20 | Spectral quadrature over 380–780 nm. |
|
|
113
|
+
| `sensor.spectralStepNanometers` | 20; 5 on exact requests under a spiky light (2.22.0, #590) | Spectral quadrature over 380–780 nm. A light is spiky when R(20) = 20²·κ/24 > 0.01, κ = Σ\|Δ²S\| / ΣS on a 1 nm grid (the midpoint rule's error term, DLMF §3.5(i)): the fluorescent and LED representative sources. Exact requests (DNG, JPEG, PNG, `fast: false`) then take 5 nm; fast ones keep 20 nm, add the assumption `sensor.spectralSpikiness` and report `diagnostics.fast.applied["sensor.spectralStepNanometers"]: true` (exact ones `false`). An explicit step wins and adds no key. |
|
|
114
114
|
| `sensor.temporalSampleCount` | 1, or 8 when the camera rotates | Midpoints across each site's exposure window. |
|
|
115
115
|
| `sensor.noiseSeed` | 1 | Output is deterministic for a given seed. |
|
|
116
116
|
| `sensor.antiAliasing` | off | 2.1: an optical low-pass (anti-aliasing) filter: `{ kind: "four-spot", separationSites? }`, `{ kind: "two-spot", axis, separationSites? }` or `{ kind: "point-splitting", components }` (see [Anti-aliasing filter](#anti-aliasing-filter)). Each record also takes `fast`. |
|
|
@@ -444,7 +444,10 @@ independent of pixel size, so the same exposure gives the same image brightness
|
|
|
444
444
|
- So the facade multiplies the system conversion gain (e⁻ per code) and the pre-ADC saturation
|
|
445
445
|
signal of every ISO regime by the area ratio pitch²/36 µm². Codes, black level, ADC and digital gain
|
|
446
446
|
are unchanged. At 6 µm the ratio is exactly 1 and nothing is changed (byte-identical to 2.1.0).
|
|
447
|
-
-
|
|
447
|
+
- Above 6 µm a pixel stands for N = pitch²/36 µm² of the tier's 6 µm photosites, so (from 2.22.0, #599) its
|
|
448
|
+
read noise is the photosite's summed in quadrature, ×√N, as for a [sensor profile](#sensor-profiles-554)
|
|
449
|
+
(EMVA 1288 Release 4.0, §6.4: binning multiplies the pixel area). Below 6 µm the read noise (in e⁻) stays
|
|
450
|
+
per pixel, and the synthetic dark current (1 e⁻/s) stays per pixel at every pitch.
|
|
448
451
|
|
|
449
452
|
What you see: brightness stays the same, and per-pixel noise rises as pixels shrink, because a
|
|
450
453
|
smaller pixel collects fewer electrons for the same exposure (shot-noise SNR = √N_e). At f/8, 1/125 s,
|
|
@@ -489,7 +492,7 @@ r.sensor.dynamicRangeStops; r.sensor.midGrey?.snrDecibels;
|
|
|
489
492
|
| --- | --- | --- | --- |
|
|
490
493
|
| `peakQuantumEfficiency` | 0.05–0.9 | 0.5 | Multiplies every channel's EQE curve by value / 0.5: the curves keep their shape and the colour matrix, and the collected charge scales in proportion. 0.9 keeps the blue channel's peak below 1. |
|
|
491
494
|
| `fullWell` | 100–1e10 e⁻ at the pitch | the body's base-ISO saturation at 6 µm, × pitch²/36 µm² | The charge at the RAW white level at base ISO (the saturation capacity of EMVA 1288 Release 4.0). `link: "pixel-area"` (default) scales `electrons`, the full well at `referencePixelPitchMicrometers` (default 6), by (pitch / reference)²; `link: "none"` uses it at any pitch. |
|
|
492
|
-
| `readNoise` | 0–100 e⁻; switch ISO 100–1e7 | the body's (tiers: 2/1.1, 3/1.4 and 6/2.5 e
|
|
495
|
+
| `readNoise` | 0–100 e⁻; switch ISO 100–1e7 | the body's (tiers: 2/1.1, 3/1.4 and 6/2.5 e⁻ per 6 µm photosite, ×√N above 6 µm; switch at ISO 800) | Read noise of one physical photosite (2.22.0, tiers and profiles alike; a rendered site that sums N photosites takes it ×√N) for the low and the high conversion gain, and `highGainFromIso`, the ISO from which the high gain is used. A switch above the body's top ISO keeps the low gain throughout. |
|
|
493
496
|
| `readoutSeconds` | 1e-6–1 s | the body's (8, 15, 30 ms) | The rolling shutter's top-to-bottom skew (`motion.shutter: "rolling"`; `motion.rollingShutterSkewSeconds` still overrides it). |
|
|
494
497
|
| `adcBitDepth` | 12, 14, 16 | the body's (consumer 12, others 14) | The black level scales by 2^Δbits (1 024 at 14 bits → 4 096 at 16), the saturation code is 2^bits − 1 and the conversion gain follows, so the same charge reaches the same fraction of the code range. A 16-bit RAW writes a 16-bit DNG with white level 65 535. |
|
|
495
498
|
| `fixedPatternNoise` | `true`/`false` | `false` | The body's synthetic fixed-pattern profile (PRNU, DSNU, hot pixels, row and column offsets), the same pattern in every capture. Not available in `simulateDisplayView`, which refuses it. |
|
|
@@ -516,6 +519,32 @@ gain) in electrons. With no read noise the range is limited by the ADC alone: lo
|
|
|
516
519
|
The Professional tier gives 14.7 stops at ISO 100, 12.7 at ISO 800 (high gain) and 9.7 at ISO 6400;
|
|
517
520
|
the Consumer tier 11.7, 10.0 and 7.0.
|
|
518
521
|
|
|
522
|
+
**Native-pixel dynamic range (#595)** (`sensor.native`, from 2.22.0): the figures above are those of
|
|
523
|
+
the rendered site, which at a pitch other than the body's own stands for N = (pitch / native pitch)²
|
|
524
|
+
photosites (`native.photositesPerSite`). `sensor.native` gives the same figures for one native
|
|
525
|
+
photosite: a generic tier's declared 6 µm reference, or a sensor profile's own pitch, with the request's
|
|
526
|
+
sensor characteristics applied as at that pitch. It has the photosite's `fullWellElectrons`,
|
|
527
|
+
`readNoiseElectrons`, engineering `dynamicRangeStops` and `dynamicRangeByIso`, and, on exact requests,
|
|
528
|
+
its `midGrey` figure (the site's charge × 1/N). For N independent photosites summed with their read noise,
|
|
529
|
+
S_sat,N = N·S_sat,0 and σ_N = √N·σ_0, so the rendered range is the native range + ½·log₂N, limited by the
|
|
530
|
+
ADC, and the rendered mid-gray SNR is √N times the native one.
|
|
531
|
+
|
|
532
|
+
- `native.emva1288`: EMVA 1288 Release 4.0 Linear (§2.7, Eq. 26–28, free from emva.org): the absolute
|
|
533
|
+
sensitivity threshold μ_e.min = √(σ_d² + σ_q² + 1/4) + 1/2 (σ_d the read noise, dark current excluded),
|
|
534
|
+
`dynamicRangeStops` = log₂(μ_e.sat / μ_e.min) and `dynamicRangeDecibels`, and the saturation capacity
|
|
535
|
+
per µm² (Table 2). This is the figure to show as "dynamic range (EMVA 1288)". It is slightly lower than
|
|
536
|
+
the engineering figure: 14.36 against 14.67 stops for the Professional tier at ISO 100.
|
|
537
|
+
- `native.normalized8Megapixel`: **a convention, not part of EMVA 1288**, for comparing bodies of
|
|
538
|
+
different resolutions: the frame's native photosites (`framePhotosites`, the output frame's area over
|
|
539
|
+
the native photosite's) resampled to 8 million pixels, each summing framePhotosites / 8 000 000
|
|
540
|
+
photosites with their read noise (the binning rule above): DR + ½·log₂(framePhotosites / 8 000 000),
|
|
541
|
+
for the engineering and the EMVA figure. No ADC limit is applied to the sum. A 6000 × 4000 frame of
|
|
542
|
+
6 µm photosites adds 0.79 stops.
|
|
543
|
+
|
|
544
|
+
All are model figures from the engine's noise model, not measurements. `sensor.readNoise` is per **physical
|
|
545
|
+
photosite** on a tier and a profile alike (2.22.0): `native.readNoiseElectrons` is the value given, and a rendered site
|
|
546
|
+
that sums N photosites takes it ×√N.
|
|
547
|
+
|
|
519
548
|
**Noise figure** (#535, `sensor.midGrey`): computed only when the request resolves to exact (`fast:
|
|
520
549
|
false`, the default for DNG, JPEG and PNG exports; about 0.3 ms); a fast request (the sRGB default)
|
|
521
550
|
omits it. The expected signal-to-noise ratio of an 18 % Lambertian
|
|
@@ -2289,10 +2318,10 @@ the exact speed-ups, in [DISPLAY_VIEW.md](https://github.com/Photivra/photivra/b
|
|
|
2289
2318
|
| 12 MP phone | Quality | 49.7 s (20×) | 106 s (3.8×) | 272 s (2.2×) |
|
|
2290
2319
|
| 100 MP medium format | Quality | 18.2 s (3.7×) | 49.7 s (1.8×) | 126 s (1.0×) |
|
|
2291
2320
|
|
|
2292
|
-
Quality costs more on small pixels. The diffraction kernel spans several real sites, and at 1.4 µm even
|
|
2293
|
-
noisy enough for the AHD correction to run on every sampled pixel
|
|
2294
|
-
the same
|
|
2295
|
-
|
|
2321
|
+
Quality costs more on small pixels. The diffraction kernel spans several real sites, and (2.6.0 to 2.21.0) at 1.4 µm even
|
|
2322
|
+
ISO 100 was noisy enough for the AHD low-signal correction to run on every sampled pixel (2.6.0: a second 384-wide phone view
|
|
2323
|
+
of the same request took 73 s instead of 103 s, reusing its states). 2.22.0 removed that correction, so these timings
|
|
2324
|
+
include work the view no longer does. In 2.6.0 the results were byte-identical to those of the display view as first
|
|
2296
2325
|
merged, which took 222 s, 450 s and 837 s on the phone; later releases changed some views' values (for example the
|
|
2297
2326
|
exact area mean, 2.8.0; AHD's choice, 2.13.0).
|
|
2298
2327
|
|