@photivra/engine 2.13.0 → 2.14.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 +68 -1
- package/CITATION.cff +1 -1
- package/README.md +7 -7
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +2 -0
- package/dist/camera/lens-projection.js +2 -1
- package/dist/camera/lens-projection.js.map +1 -1
- package/dist/capture/dng-import.js +1 -1
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/host-yield-cadence.js +1 -1
- package/dist/capture/host-yield-cadence.js.map +1 -1
- package/dist/capture/native-environment-optical-stage.js +1 -1
- package/dist/capture/native-environment-optical-stage.js.map +1 -1
- package/dist/capture/native-raw.js +1 -1
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/packed-sensor-raw.js +3 -3
- package/dist/capture/packed-sensor-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.js +1 -1
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/color/capture-color.js +3 -3
- package/dist/color/capture-color.js.map +1 -1
- package/dist/color/chromatic-adaptation.js +2 -2
- package/dist/color/chromatic-adaptation.js.map +1 -1
- package/dist/color/color-matching-functions.js +1 -1
- package/dist/color/color-matching-functions.js.map +1 -1
- package/dist/color/colorimetry-math.js +2 -2
- package/dist/color/colorimetry-math.js.map +1 -1
- package/dist/color/illuminants.js +3 -3
- package/dist/color/illuminants.js.map +1 -1
- package/dist/color/rgb-color-spaces.js +1 -1
- package/dist/color/rgb-color-spaces.js.map +1 -1
- package/dist/color/sensor-color-development.js +1 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/core/record-validation.js +3 -3
- package/dist/core/record-validation.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-prime-lenses.js +3 -3
- package/dist/equipment/generic-prime-lenses.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +23 -1
- package/dist/equipment/generic-tier-assets.js +58 -30
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.d.ts +6 -3
- package/dist/equipment/generic-tier-presets.js +26 -13
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/exposure/auto-iso-slowest-shutter.js +2 -1
- package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
- package/dist/exposure/flash-ttl.js +1 -1
- package/dist/exposure/flash-ttl.js.map +1 -1
- package/dist/exposure/flash.js +1 -1
- package/dist/exposure/flash.js.map +1 -1
- package/dist/facade/chart-material-layers.js +2 -2
- package/dist/facade/chart-material-layers.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +1 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/chart-scenes.d.ts +45 -8
- package/dist/facade/chart-scenes.js +101 -44
- package/dist/facade/chart-scenes.js.map +1 -1
- package/dist/facade/color-patches.d.ts +129 -0
- package/dist/facade/color-patches.js +209 -0
- package/dist/facade/color-patches.js.map +1 -0
- package/dist/facade/display-view-gpu.js +1 -1
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/scene-radiance.js +2 -2
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +47 -3
- package/dist/facade/simulate-capture.js +71 -22
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +2 -2
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.js +2 -2
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.js +1 -1
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/image-kernel-execution.js +2 -2
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/temporal-sum-gpu.js +23 -23
- package/dist/gpu/webgpu-runtime.js +1 -1
- package/dist/gpu/webgpu-runtime.js.map +1 -1
- package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
- package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
- package/dist/gpu/wgsl-chart-kernel.js +117 -117
- package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.js +505 -505
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-kernels.js +203 -203
- package/dist/gpu/wgsl-kernels.js.map +1 -1
- package/dist/gpu/wgsl-sensor-kernel.js +302 -302
- package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/motion/temporal-exposure-sum.js +1 -1
- package/dist/motion/temporal-exposure-sum.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.js +4 -4
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/dist/optics/front-of-lens-filter.js +1 -1
- package/dist/optics/front-of-lens-filter.js.map +1 -1
- package/dist/optics/irradiance-convolution.js +1 -1
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/output/native-capture-sdr.js +1 -1
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/png-encoder.js +2 -2
- package/dist/output/png-encoder.js.map +1 -1
- package/dist/output/print-jpeg.js +1 -1
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/sensor/fixed-pattern-noise.js +1 -1
- package/dist/sensor/fixed-pattern-noise.js.map +1 -1
- package/dist/sensor/readout-exposure-linkage.js +1 -1
- package/dist/sensor/readout-exposure-linkage.js.map +1 -1
- package/docs/API_REFERENCE.md +29 -1
- package/docs/QUICK_START.md +40 -5
- package/docs/USAGE.md +35 -1
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-tier-assets.md +41 -1
- package/docs/api/equipment-generic-tier-presets.md +11 -3
- package/docs/api/exports.json +107 -2
- package/docs/api/facade-chart-scenes.md +36 -5
- package/docs/api/facade-color-patches.md +150 -0
- package/docs/api/facade-simulate-capture.md +52 -2
- package/llms.txt +1 -0
- package/package.json +2 -2
|
@@ -91,13 +91,14 @@ import { resolveChartMaterialLayers } from "./chart-material-layers.js";
|
|
|
91
91
|
import { resolveCaptureIlluminant, calculateIlluminantIrradianceScale, buildPlanarSceneRadianceProvider, requireCallerScene, FACADE_SPECTRAL_RANGE_NANOMETERS, resolveChartSizeOption, PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W, planarProviderMaterialOfDirection } from "./scene-radiance.js";
|
|
92
92
|
/** Version of how the facade builds requests. It changes whenever the same input would build a different request. */
|
|
93
93
|
export const SIMULATE_CAPTURE_FACADE_VERSION = "1.0.0";
|
|
94
|
+
export const SIMULATE_CAPTURE_METERING_MODES = /* @__PURE__ */ Object.freeze(["scene-average", "grey-card"]);
|
|
94
95
|
/** Default midpoint samples per axis of `sensor.pixelAperture: "box"`. */
|
|
95
96
|
export const SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES = 4;
|
|
96
97
|
/**
|
|
97
98
|
* Options reported in `diagnostics.options` (2.1), in this order, when they ran: the ones that change how much work
|
|
98
99
|
* a capture does. `motion.rotation` is a non-zero angular velocity; `sensor.temporalSampleCount` a count above 1.
|
|
99
100
|
*/
|
|
100
|
-
export const SIMULATE_CAPTURE_COST_OPTIONS = Object.freeze(["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf",
|
|
101
|
+
export const SIMULATE_CAPTURE_COST_OPTIONS = /* @__PURE__ */ Object.freeze(["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf",
|
|
101
102
|
"lens.diffraction", "lens.defocus", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region",
|
|
102
103
|
"output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual",
|
|
103
104
|
"output.format.jpeg", "output.format.png", "output.format.dng"]);
|
|
@@ -106,10 +107,10 @@ export const SIMULATE_CAPTURE_COST_OPTIONS = Object.freeze(["lens.pupilSampling"
|
|
|
106
107
|
* the record form with `fast` and is reported in `diagnostics.fast.applied` when it runs. Every one of them takes the flag,
|
|
107
108
|
* whether or not it has a fast method yet; one without runs exact and reports `false`.
|
|
108
109
|
*/
|
|
109
|
-
export const SIMULATE_CAPTURE_FAST_OPTIONS = Object.freeze(["lens.diffraction", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"]);
|
|
110
|
-
export const FACADE_EVIDENCE = Object.freeze([Object.freeze({ sourceOrigin: "photivra",
|
|
110
|
+
export const SIMULATE_CAPTURE_FAST_OPTIONS = /* @__PURE__ */ Object.freeze(["lens.diffraction", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"]);
|
|
111
|
+
export const FACADE_EVIDENCE = /* @__PURE__ */ Object.freeze([/* @__PURE__ */ Object.freeze({ sourceOrigin: "photivra",
|
|
111
112
|
sourceReference: "photivra:simulate-capture-facade", reuseStatus: "photivra-owned" })]);
|
|
112
|
-
const DEFAULTS = Object.freeze({ camera: "professional", fNumber: 8, shutterSeconds: 1 / 125, iso: 100, compensationStops: 0,
|
|
113
|
+
const DEFAULTS = /* @__PURE__ */ Object.freeze({ camera: "professional", fNumber: 8, shutterSeconds: 1 / 125, iso: 100, compensationStops: 0,
|
|
113
114
|
subjectDistanceM: 5, width: 64, height: 64, format: "srgb-8", toneCurve: "identity", highlightMode: "clip",
|
|
114
115
|
whiteBalance: "scene-illuminant", jpegQuantizationStep: 2, pixelPitchMicrometers: 6, spectralStepNanometers: 20,
|
|
115
116
|
temporalSampleCount: 1, noiseSeed: 1, illuminantKelvin: 6500 });
|
|
@@ -153,6 +154,18 @@ function standardExposureIlluminanceLux(fNumber, shutterSeconds, iso, meteredLum
|
|
|
153
154
|
meteredLuminance + " cd/m²).");
|
|
154
155
|
return lux;
|
|
155
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* 2.14: deviation, in stops, of the settings from the standard exposure of an 18 % Lambertian grey card under illuminance E:
|
|
159
|
+
* Δ_grey = log2(L_ref(N, t, S) / (0.18·E/π)), computed as log2((π·L_ref/0.18) / E) so that at the default settings the
|
|
160
|
+
* numerator is SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX in the same operations, and the default request gives exactly 0.
|
|
161
|
+
* The grey card's luminance L = ρ·E/π is that of a Lambertian reflector (luminance = illuminance × reflectance / π).
|
|
162
|
+
*/
|
|
163
|
+
function greyCardDeviationStops(fNumber, shutterSeconds, iso, illuminanceLux) {
|
|
164
|
+
const deviation = Math.log2(Math.PI * sosReferenceLuminance(fNumber, shutterSeconds, iso) / GREY_CARD_REFLECTANCE / illuminanceLux);
|
|
165
|
+
if (!Number.isFinite(deviation))
|
|
166
|
+
throw new InvalidScientificResultError("exposure.greyCardDeviationStops is not finite (illuminance " + illuminanceLux + " lux).");
|
|
167
|
+
return deviation;
|
|
168
|
+
}
|
|
156
169
|
/**
|
|
157
170
|
* Default illuminance, lux: an 18 % Lambertian grey card (L = 0.18·E/π) then gets exactly the CIPA DC-004 standard
|
|
158
171
|
* output sensitivity reference exposure H = 10/S at f/8, 1/125 s, ISO 100:
|
|
@@ -284,7 +297,7 @@ export function apertureNodeOffsets(n) {
|
|
|
284
297
|
const AIRY_THIRD_ZERO_PER_LAMBDA_N = 10.173468135062722 / Math.PI;
|
|
285
298
|
/** Default temporal midpoints when the camera rotates (2.0 motion option). */
|
|
286
299
|
const ROTATING_TEMPORAL_SAMPLE_COUNT = 8;
|
|
287
|
-
const IDS = Object.freeze({ frame: "facade-frame", mode: "native-still", cfa: "photivra-generic-bayer", binding: "facade-native-cfa",
|
|
300
|
+
const IDS = /* @__PURE__ */ Object.freeze({ frame: "facade-frame", mode: "native-still", cfa: "photivra-generic-bayer", binding: "facade-native-cfa",
|
|
288
301
|
sceneState: "facade-scene-state", scene: "facade-scene", provider: "facade-provider", lights: "facade-lights", materials: "facade-materials",
|
|
289
302
|
capture: "facade-capture", spectral: "facade-colorimetric-eqe", sampling: "facade-sampling", stack: "facade-no-aa", stackAa: "facade-aa",
|
|
290
303
|
stationarity: "facade-stationarity", operatingState: "facade-operating-state", dark: "facade-dark", completeness: "facade-completeness",
|
|
@@ -296,8 +309,8 @@ export const CHANNELS = ["red", "green", "blue"];
|
|
|
296
309
|
* proportional to CIE XYZ (the Luther–Ives condition holds, up to quadrature error), and the camera→XYZ
|
|
297
310
|
* matrix is diagonal.
|
|
298
311
|
*/
|
|
299
|
-
const EQE_SCALE = Object.freeze({ red: 0.5, green: 0.5, blue: 0.25 });
|
|
300
|
-
const CMF_KEY = Object.freeze({ red: "x", green: "y", blue: "z" });
|
|
312
|
+
const EQE_SCALE = /* @__PURE__ */ Object.freeze({ red: 0.5, green: 0.5, blue: 0.25 });
|
|
313
|
+
const CMF_KEY = /* @__PURE__ */ Object.freeze({ red: "x", green: "y", blue: "z" });
|
|
301
314
|
export const CAMERA_TO_XYZ = Object.freeze([[EQE_SCALE.green / EQE_SCALE.red, 0, 0], [0, 1, 0], [0, 0, EQE_SCALE.green / EQE_SCALE.blue]].map((r) => Object.freeze(r)));
|
|
302
315
|
const XYZ_TO_CAMERA = [[EQE_SCALE.red / EQE_SCALE.green, 0, 0], [0, 1, 0], [0, 0, EQE_SCALE.blue / EQE_SCALE.green]];
|
|
303
316
|
/** Synthetic dark current per site at the operating temperature, e⁻/s. */
|
|
@@ -305,7 +318,7 @@ export const DARK_CURRENT_ELECTRONS_PER_SECOND = 1;
|
|
|
305
318
|
const OPERATING_TEMPERATURE_C = 20;
|
|
306
319
|
export function fail(message) { throw new InvalidConfigurationError(message); }
|
|
307
320
|
/** Where `output.metadata` goes in each file format (reported in the `output.metadata` assumption). */
|
|
308
|
-
const TEXT_METADATA_LAYOUT = Object.freeze({
|
|
321
|
+
const TEXT_METADATA_LAYOUT = /* @__PURE__ */ Object.freeze({
|
|
309
322
|
jpeg: "XMP packet in APP1 (dc:description, xmp:CreatorTool, dc:rights, exif:UserComment), plus a COM segment holding the comment",
|
|
310
323
|
png: "One tEXt (printable Latin-1) or iTXt (UTF-8) chunk per field, keywords Description, Software, Copyright, Comment, before the first IDAT",
|
|
311
324
|
dng: "ImageDescription (statement, then the description) and Copyright when printable ASCII; an XMP packet in tag 700 with every field"
|
|
@@ -390,6 +403,19 @@ function luminanceOf(radiance) {
|
|
|
390
403
|
}
|
|
391
404
|
return PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W * sum;
|
|
392
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* 2.14: the capabilities with the ISO grid cut to its whole stops (ISO 100·2^k, as the 2.0.0 tier bodies list them), for
|
|
408
|
+
* Auto ISO's default `isoSteps: "whole-stop"`. A whole-stop grid is returned unchanged, so 2.0.0 records resolve as before.
|
|
409
|
+
*/
|
|
410
|
+
function wholeStopIsoCapabilities(capabilities) {
|
|
411
|
+
const grid = capabilities.iso.settingGrid;
|
|
412
|
+
if (grid.kind !== "discrete-values")
|
|
413
|
+
return capabilities;
|
|
414
|
+
const whole = grid.values.filter((iso) => Number.isInteger(Math.log2(iso / 100)));
|
|
415
|
+
if (whole.length === grid.values.length || whole.length === 0)
|
|
416
|
+
return capabilities;
|
|
417
|
+
return { ...capabilities, iso: { ...capabilities.iso, settingGrid: { kind: "discrete-values", values: Object.freeze(whole) } } };
|
|
418
|
+
}
|
|
393
419
|
/** The white balance measured from a perfect white diffuser under the scene illuminant ("scene-illuminant"). */
|
|
394
420
|
export function sceneIlluminantWhiteBalance(illuminant) {
|
|
395
421
|
const xyz = illuminantXyz(illuminant), camera = XYZ_TO_CAMERA.map((row) => row[0] * xyz[0] + row[1] * xyz[1] + row[2] * xyz[2]);
|
|
@@ -428,7 +454,7 @@ export function bilinearPhaseProfiles() {
|
|
|
428
454
|
* #516: the result's Auto ISO report, read from a resolved Auto ISO resolution (every mode's resolved Auto ISO branch carries
|
|
429
455
|
* `idealIsoBeforeConstraints`, `isoResolution` and a resolved `targetResidual`; aperture priority adds its fallback flag).
|
|
430
456
|
*/
|
|
431
|
-
function autoIsoReport(resolution, iso, shutterPolicy, limits, automatic) {
|
|
457
|
+
function autoIsoReport(resolution, iso, shutterPolicy, limits, automatic, isoSteps) {
|
|
432
458
|
const r = resolution;
|
|
433
459
|
// Program reports its hold under slowestPreferredShutter (#504); aperture priority at the top level.
|
|
434
460
|
const program = r.slowestPreferredShutter;
|
|
@@ -441,7 +467,7 @@ function autoIsoReport(resolution, iso, shutterPolicy, limits, automatic) {
|
|
|
441
467
|
...(limits ? { limits: { ...limits } } : {}), limitingConstraint: r.targetResidual.limitingConstraint, residualStops: residual,
|
|
442
468
|
...(shutterPolicy ? { ...shutterPolicy, afterMaximumIsoFallbackUsed: (program ? program.afterMaximumIsoFallbackUsed : r.afterMaximumIsoFallbackUsed) === true } : {}),
|
|
443
469
|
...(program ? { slowestPreferredShutterApplied: program.applied === true } : {}),
|
|
444
|
-
...(automatic ? { automaticSlowestShutter: automatic } : {}) };
|
|
470
|
+
...(automatic ? { automaticSlowestShutter: automatic } : {}), ...(isoSteps ? { isoSteps } : {}) };
|
|
445
471
|
}
|
|
446
472
|
/**
|
|
447
473
|
* #505: `exposure.autoIso.limits`, the photographer's Auto ISO range. Both limits are selectable ISOs inside the body's
|
|
@@ -1304,7 +1330,11 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1304
1330
|
registerInternalRadianceAccess(provider.evaluateRadiance, { keyOfDirection: materialOfDirection, radianceOfKey: provider.materialRadiance, tabulated: true });
|
|
1305
1331
|
assume("model.scene-radiometry", "model", "lambertian-plane", "Radiance = reflectance × irradiance / π on one plane; illuminance from K_m = 683 lm/W and the engine's analytic CIE 1931 ȳ fit.");
|
|
1306
1332
|
// Metering (CIPA DC-004 standard output sensitivity and camera equation) and exposure-mode resolution
|
|
1307
|
-
const exposureOptions = allowKeys(top.exposure, "exposure", ["mode", "iso", "shutterSeconds", "compensationStops", "autoIso"]);
|
|
1333
|
+
const exposureOptions = allowKeys(top.exposure, "exposure", ["mode", "iso", "shutterSeconds", "compensationStops", "autoIso", "metering"]);
|
|
1334
|
+
// 2.14: no default assumption when omitted, so 2.13 requests keep their assumptions; the result reports `exposure.metering`.
|
|
1335
|
+
const metering = exposureOptions.metering === undefined ? "scene-average" : exposureOptions.metering;
|
|
1336
|
+
if (!SIMULATE_CAPTURE_METERING_MODES.includes(metering))
|
|
1337
|
+
fail('exposure.metering must be "scene-average" or "grey-card".');
|
|
1308
1338
|
const mode = pick(exposureOptions, "mode", "exposure.mode", "manual", "Manual exposure.");
|
|
1309
1339
|
if (!["manual", "aperture-priority", "shutter-priority", "program"].includes(mode))
|
|
1310
1340
|
fail("Unknown exposure mode.");
|
|
@@ -1336,6 +1366,15 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1336
1366
|
}
|
|
1337
1367
|
const meteredLuminance = luminanceSum / (zones * zones);
|
|
1338
1368
|
assume("exposure.metering", "model", { policy: meterProfile.policy.kind, zones: zones * zones }, "The body's " + meterProfile.policy.kind + " meter reads scene luminance on a 16 × 16 zone grid, before the lens (no lens transmission, vignetting or flare).");
|
|
1369
|
+
if (metering === "grey-card") {
|
|
1370
|
+
// 2.14: incident metering. Every zone reads the 18 % Lambertian grey card under the request's illuminance, L = 0.18·E/π.
|
|
1371
|
+
if (callerScene?.kind === "radiance-function")
|
|
1372
|
+
fail('exposure.metering "grey-card" needs a scene lit by the illuminance; a radiance-function scene ignores illuminanceLux.');
|
|
1373
|
+
const cardLuminance = GREY_CARD_REFLECTANCE * illuminanceLux / Math.PI;
|
|
1374
|
+
for (const sample of zoneSamples)
|
|
1375
|
+
sample.relativeLinearSignal = meterProfile.target.targetRelativeSignal * cardLuminance / referenceLuminance;
|
|
1376
|
+
assume("exposure.meteringSource", "model", { metering, greyCardReflectance: GREY_CARD_REFLECTANCE, luminanceCdPerSquareMeter: cardLuminance }, "Grey-card (incident) metering: every meter zone reads an 18 % Lambertian grey card under the scene illuminance, L = 0.18·E/π, instead of the scene.");
|
|
1377
|
+
}
|
|
1339
1378
|
const meterResult = meterRelativeExposure({ profile: meterProfile, sampleSet: { measurementId: "facade-meter", sceneStateId: IDS.sceneState,
|
|
1340
1379
|
inputDomain: "relative-pre-exposure-linear-signal", captureRegion: "oriented-active-capture", captureGeometry: resolvedGeometry,
|
|
1341
1380
|
processingState: { exposureSettingsApplied: false, whiteBalanceApplied: false, toneMappingApplied: false, displayGammaApplied: false, sharpeningApplied: false },
|
|
@@ -1372,16 +1411,25 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1372
1411
|
// #504: the slowest preferred shutter (aperture priority; optional in program) and, with "auto", how it was derived.
|
|
1373
1412
|
let autoIsoShutterPolicy;
|
|
1374
1413
|
let automaticSlowestShutter;
|
|
1414
|
+
// 2.14: the capabilities the exposure-mode resolvers see; with Auto ISO in whole stops, the ISO grid's whole stops only.
|
|
1415
|
+
let resolverCapabilities = capabilities, autoIsoIsoSteps = "whole-stop";
|
|
1375
1416
|
if (automaticIso) {
|
|
1376
1417
|
if (capabilities.iso.autoIsoAvailability !== "supported")
|
|
1377
1418
|
fail('exposure.iso "auto" needs a body with Auto ISO; this camera\'s Auto ISO is ' + capabilities.iso.autoIsoAvailability + ".");
|
|
1378
|
-
autoIsoOptions = allowKeys(exposureOptions.autoIso, "exposure.autoIso", ["slowestPreferredShutterSeconds", "afterMaximumIso", "limits", "automaticSlowestShutter"]);
|
|
1419
|
+
autoIsoOptions = allowKeys(exposureOptions.autoIso, "exposure.autoIso", ["slowestPreferredShutterSeconds", "afterMaximumIso", "limits", "automaticSlowestShutter", "isoSteps"]);
|
|
1420
|
+
// 2.14: Auto ISO steps in whole stops unless asked for thirds, so its choices are those of 2.13 on the third-stop bodies.
|
|
1421
|
+
if (autoIsoOptions.isoSteps !== undefined && autoIsoOptions.isoSteps !== "whole-stop" && autoIsoOptions.isoSteps !== "third-stop")
|
|
1422
|
+
fail('exposure.autoIso.isoSteps must be "whole-stop" or "third-stop".');
|
|
1423
|
+
if (autoIsoOptions.isoSteps === "third-stop")
|
|
1424
|
+
autoIsoIsoSteps = "third-stop";
|
|
1425
|
+
if (autoIsoIsoSteps === "whole-stop")
|
|
1426
|
+
resolverCapabilities = wholeStopIsoCapabilities(capabilities);
|
|
1379
1427
|
if (mode !== "aperture-priority" && mode !== "program")
|
|
1380
1428
|
for (const key of ["slowestPreferredShutterSeconds", "afterMaximumIso", "automaticSlowestShutter"])
|
|
1381
1429
|
if (autoIsoOptions[key] !== undefined)
|
|
1382
1430
|
fail("exposure.autoIso." + key + ' applies only to exposure.mode "aperture-priority" or "program"; omit it.');
|
|
1383
1431
|
if (autoIsoOptions.limits !== undefined)
|
|
1384
|
-
autoIsoLimits = parseAutoIsoLimitsOption(autoIsoOptions.limits,
|
|
1432
|
+
autoIsoLimits = parseAutoIsoLimitsOption(autoIsoOptions.limits, resolverCapabilities.iso);
|
|
1385
1433
|
assume("exposure.autoIso", "model", { minimum: capabilities.iso.autoIsoMinimum ?? capabilities.iso.minimum, maximum: capabilities.iso.autoIsoMaximum ?? capabilities.iso.maximum,
|
|
1386
1434
|
...(autoIsoLimits ? { limits: { ...autoIsoLimits } } : {}), ...autoIsoBaseline }, "Auto ISO: the engine's exposure-mode resolver chooses the ISO from the meter target, starting from the lowest selectable ISO in the body's Auto ISO range and " +
|
|
1387
1435
|
"snapping to the nearest selectable ISO in log2 (the lower on a tie), held at the range's ends. A generic policy, not a manufacturer's Auto ISO." +
|
|
@@ -1419,25 +1467,25 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1419
1467
|
}
|
|
1420
1468
|
const limitsPolicy = autoIsoLimits ? { autoIsoLimits: { ...autoIsoLimits } } : {};
|
|
1421
1469
|
if (mode === "manual") {
|
|
1422
|
-
resolution = resolveManualExposureMode({ target, capabilities, referenceExposure: reference, manualAperture: fNumber, manualShutterSeconds: shutterSeconds,
|
|
1470
|
+
resolution = resolveManualExposureMode({ target, capabilities: resolverCapabilities, referenceExposure: reference, manualAperture: fNumber, manualShutterSeconds: shutterSeconds,
|
|
1423
1471
|
isoControl: automaticIso ? { kind: "automatic", quantizationPolicy: autoIsoBaseline.isoQuantizationPolicy, ...limitsPolicy } : { kind: "manual", iso } });
|
|
1424
1472
|
}
|
|
1425
1473
|
else if (autoIsoAperturePriority) {
|
|
1426
|
-
resolution = resolveAperturePriorityAutoIsoExposureMode({ target, capabilities, referenceExposure: reference, manualAperture: fNumber,
|
|
1474
|
+
resolution = resolveAperturePriorityAutoIsoExposureMode({ target, capabilities: resolverCapabilities, referenceExposure: reference, manualAperture: fNumber,
|
|
1427
1475
|
policy: { kind: "minimum-iso-until-slowest-preferred-shutter", ...autoIsoBaseline, shutterSelectionPolicy: "not-longer-than-target", ...autoIsoAperturePriority, ...limitsPolicy } });
|
|
1428
1476
|
}
|
|
1429
1477
|
else if (mode === "aperture-priority") {
|
|
1430
|
-
resolution = resolveAperturePriorityExposureMode({ target, capabilities, referenceExposure: reference, manualAperture: fNumber, manualIso: iso,
|
|
1478
|
+
resolution = resolveAperturePriorityExposureMode({ target, capabilities: resolverCapabilities, referenceExposure: reference, manualAperture: fNumber, manualIso: iso,
|
|
1431
1479
|
shutterQuantizationPolicy: "nearest-log2-shorter-on-tie" });
|
|
1432
1480
|
}
|
|
1433
1481
|
else if (mode === "shutter-priority") {
|
|
1434
|
-
resolution = resolveShutterPriorityExposureMode({ target, capabilities, referenceExposure: reference, manualShutterSeconds: shutterSeconds,
|
|
1482
|
+
resolution = resolveShutterPriorityExposureMode({ target, capabilities: resolverCapabilities, referenceExposure: reference, manualShutterSeconds: shutterSeconds,
|
|
1435
1483
|
isoControl: automaticIso ? { kind: "automatic", policy: { kind: "minimum-iso-aperture-first", apertureSelectionPolicy: "not-wider-than-target", ...autoIsoBaseline, ...limitsPolicy } } :
|
|
1436
1484
|
{ kind: "manual", iso, apertureQuantizationPolicy: "nearest-log2-narrower-on-tie" } });
|
|
1437
1485
|
}
|
|
1438
1486
|
else {
|
|
1439
1487
|
assume("exposure.programLine", "model", "facade-generic-program-line", "Engine-authored generic program line (f/16 1/2000 s … f/8 1/125 s … f/4 1.024 s); not a manufacturer program.");
|
|
1440
|
-
resolution = resolveProgramAutoExposureMode({ target, capabilities, referenceExposure: reference, programLine: defaultProgramLine(),
|
|
1488
|
+
resolution = resolveProgramAutoExposureMode({ target, capabilities: resolverCapabilities, referenceExposure: reference, programLine: defaultProgramLine(),
|
|
1441
1489
|
isoControl: automaticIso ? { kind: "automatic", ...autoIsoBaseline, ...limitsPolicy, ...(autoIsoShutterPolicy ? { slowestPreferredShutter: { ...autoIsoShutterPolicy } } : {}) } :
|
|
1442
1490
|
{ kind: "manual", iso } });
|
|
1443
1491
|
}
|
|
@@ -1448,9 +1496,10 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1448
1496
|
const resolvedIso = resolution.resolvedSettings.iso ?? iso;
|
|
1449
1497
|
if (!Number.isFinite(resolvedIso) || resolvedIso <= 0)
|
|
1450
1498
|
fail("Exposure could not be resolved: the resolved ISO is not a positive finite number.");
|
|
1451
|
-
const isoReport = automaticIso ? { isoControl: "automatic", autoIso: autoIsoReport(resolution,
|
|
1499
|
+
const isoReport = automaticIso ? { isoControl: "automatic", autoIso: autoIsoReport(resolution, resolverCapabilities.iso, autoIsoShutterPolicy, autoIsoLimits, automaticSlowestShutter, autoIsoOptions.isoSteps === undefined ? undefined : autoIsoIsoSteps) } : { isoControl: "manual" };
|
|
1452
1500
|
const exposureDeviationStops = standardExposureDeviationStops(fNumber, shutterSeconds, resolvedIso, meteredLuminance);
|
|
1453
1501
|
const exposureIlluminanceLux = standardExposureIlluminanceLux(fNumber, shutterSeconds, resolvedIso, meteredLuminance, illuminanceLux, callerScene?.kind !== "radiance-function");
|
|
1502
|
+
const exposureGreyCardDeviationStops = callerScene?.kind === "radiance-function" ? null : greyCardDeviationStops(fNumber, shutterSeconds, resolvedIso, illuminanceLux);
|
|
1454
1503
|
const defocusDisc = defocusModel === undefined ? undefined :
|
|
1455
1504
|
resolveDefocusOption({ run, assume, focalLengthMm, fNumber, focusDistanceM, subjectDistanceM, pitch, diffraction: diffractionRadiusOption !== undefined });
|
|
1456
1505
|
// #492: the capture applies the disc as an object-space convolution, which needs every disc point a site sees to be another site's scene point.
|
|
@@ -1891,7 +1940,7 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1891
1940
|
// Display view: the renderer that simulate-display-view.ts passes in reads this request's resolved state, so a bundle
|
|
1892
1941
|
// that only captures does not contain the display view.
|
|
1893
1942
|
if (displayView)
|
|
1894
|
-
return await renderView({ ahd, fastReport, antiAliasing, aspect, assume, assumptions, axisChannelResponse, boxSamples, buildProvider, callerScene, chart, materialLayers, chartReport, chartSize, diffraction, defocus: displayViewDefocus, diffractionPsf, fNumber, focalLengthMm, focusDistanceM, height, highlightMode, illuminanceLux, illuminant, illuminantOption, imagingArea, intentOf, metresPerFrameUnit, noiseSeed, outputOptions, parsedView, pitch, preset, provider, regimeByChannel, resolvedIso, isoReport, standardExposureDeviationStops: exposureDeviationStops, standardExposureIlluminanceLux: exposureIlluminanceLux, sceneOption, scenePreset, shutterSeconds, started, subjectDistanceM, toneCurve, whiteBalanceOption, whiteBalanceState, width, checkAbort, margin, mode, nativeHeight, nativeWidth, sourceSha256, yieldControl, kelvinWhiteBalanceInput });
|
|
1943
|
+
return await renderView({ ahd, fastReport, antiAliasing, aspect, assume, assumptions, axisChannelResponse, boxSamples, buildProvider, callerScene, chart, materialLayers, chartReport, chartSize, diffraction, defocus: displayViewDefocus, diffractionPsf, fNumber, focalLengthMm, focusDistanceM, height, highlightMode, illuminanceLux, illuminant, illuminantOption, imagingArea, intentOf, metresPerFrameUnit, noiseSeed, outputOptions, parsedView, pitch, preset, provider, regimeByChannel, resolvedIso, isoReport, standardExposureDeviationStops: exposureDeviationStops, standardExposureIlluminanceLux: exposureIlluminanceLux, greyCardDeviationStops: exposureGreyCardDeviationStops, metering, sceneOption, scenePreset, shutterSeconds, started, subjectDistanceM, toneCurve, whiteBalanceOption, whiteBalanceState, width, checkAbort, margin, mode, nativeHeight, nativeWidth, sourceSha256, yieldControl, kelvinWhiteBalanceInput });
|
|
1895
1944
|
// Ideal reference (2.3): the scene without camera effects, at the capture's own exposure, white balance and encoding.
|
|
1896
1945
|
if (idealReference) {
|
|
1897
1946
|
const referenceStarted = clockMs();
|
|
@@ -2255,8 +2304,8 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
2255
2304
|
request: { environmentRaw: finalRawInput, siteInput, development, whiteBalanceState,
|
|
2256
2305
|
...(diffraction ? { diffraction: { profile: diffraction, psf: diffractionPsf, execution: diffractionObjectSpace ? "object-space-convolution" : "environment-psf-quadrature" } } : {}) },
|
|
2257
2306
|
exposure: { mode, settings: { fNumber, shutterSeconds, iso: resolvedIso }, ev100: Math.log2(fNumber ** 2 / shutterSeconds), ...isoReport,
|
|
2258
|
-
meteredSceneLuminanceCdPerSquareMeter: meteredLuminance, standardExposureDeviationStops: exposureDeviationStops,
|
|
2259
|
-
standardExposureIlluminanceLux: exposureIlluminanceLux, resolution },
|
|
2307
|
+
metering, meteredSceneLuminanceCdPerSquareMeter: meteredLuminance, standardExposureDeviationStops: exposureDeviationStops,
|
|
2308
|
+
standardExposureIlluminanceLux: exposureIlluminanceLux, greyCardDeviationStops: exposureGreyCardDeviationStops, resolution },
|
|
2260
2309
|
scene: { sceneId: chart ? "chart:" + chart.preset : callerScene.sceneId, preset: scenePreset, parameters: chart ? chart.parameters : {},
|
|
2261
2310
|
illuminant: { id: illuminant.id, description: illuminant.description, illuminanceLux }, distanceM: subjectDistanceM,
|
|
2262
2311
|
framedWidthM: 2 * metresPerFrameUnit, framedHeightM: 2 * metresPerFrameUnit * aspect, ...(chartReport ? { chart: chartReport } : {}), sourceSha256 },
|