@photivra/engine 2.17.0 → 2.18.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 +49 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/camera/sensor-equivalence.d.ts +156 -0
- package/dist/camera/sensor-equivalence.js +208 -0
- package/dist/camera/sensor-equivalence.js.map +1 -0
- package/dist/capture/native-environment-tabulated.d.ts +5 -0
- package/dist/capture/native-environment-tabulated.js +1 -1
- package/dist/capture/native-environment-tabulated.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/ideal-lens.d.ts +25 -0
- package/dist/equipment/ideal-lens.js +21 -0
- package/dist/equipment/ideal-lens.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +94 -0
- package/dist/facade/catalogue-lens-signal.js +362 -0
- package/dist/facade/catalogue-lens-signal.js.map +1 -0
- package/dist/facade/defocus-image-space-provider.js +5 -2
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +32 -1
- package/dist/facade/display-view-lens-signal.js +85 -10
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +8 -0
- package/dist/facade/expected-signal-setup.js +7 -0
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +5 -0
- package/dist/facade/expected-signal.js +5 -1
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +1 -0
- package/dist/facade/generic-lens-capture.js +1 -0
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +31 -4
- package/dist/facade/simulate-capture.js +117 -38
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +1 -1
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/docs/AGENT_GUIDE.md +3 -0
- package/docs/API_REFERENCE.md +29 -1
- package/docs/QUICK_START.md +49 -1
- package/docs/USAGE.md +43 -2
- package/docs/api/camera-sensor-equivalence.md +209 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-ideal-lens.md +26 -0
- package/docs/api/exports.json +67 -2
- package/docs/api/facade-simulate-capture.md +27 -4
- package/llms.txt +2 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { type CaptureWhiteBalanceIntent } from "../capture/simulated-capture.js"
|
|
|
6
6
|
import { type NativeRawOutput, type NativeRawPlan } from "../capture/native-raw.js";
|
|
7
7
|
import { type DisplayViewExpectedSignal, type DisplayViewExpectedSignalInput } from "./expected-signal.js";
|
|
8
8
|
import type { DisplayViewFastReport } from "./display-view-gpu.js";
|
|
9
|
+
import type { DisplayViewLensPsf } from "./display-view-lens-signal.js";
|
|
9
10
|
import { type NativeEnvironmentRawInput } from "../capture/native-environment-raw.js";
|
|
10
11
|
import { type NativeRawDevelopmentInput, type RawHighlightDiagnostics } from "../capture/native-raw-development.js";
|
|
11
12
|
import type { RawFrameReconstructionPhaseProfile } from "../capture/raw-frame-reconstruction.js";
|
|
@@ -156,6 +157,11 @@ export type SimulateCaptureColorEncoding = "srgb" | "display-p3" | "rec2020";
|
|
|
156
157
|
* edge-directed method that reduces zipper and false colour on sharp edges.
|
|
157
158
|
*/
|
|
158
159
|
export type SimulateCaptureDemosaic = "bilinear" | "ahd";
|
|
160
|
+
/** `lens.catalogue` as a record (#568): the lens id as `kind` and the `fast` override (see {@link SimulateCaptureOptions.fast}). */
|
|
161
|
+
export interface SimulateCaptureLensCatalogueRecord {
|
|
162
|
+
kind: string;
|
|
163
|
+
fast?: boolean;
|
|
164
|
+
}
|
|
159
165
|
/** `output.demosaic` as a record (#395): the demosaic and its `fast` override (see {@link SimulateCaptureOptions.fast}). */
|
|
160
166
|
export interface SimulateCaptureDemosaicRecord {
|
|
161
167
|
kind: SimulateCaptureDemosaic;
|
|
@@ -299,6 +305,22 @@ export interface SimulateCaptureOptions {
|
|
|
299
305
|
* focus distance below its minimum focus distance, is rejected. Not combinable with `focalLengthMm`.
|
|
300
306
|
*/
|
|
301
307
|
prime?: number;
|
|
308
|
+
/**
|
|
309
|
+
* Opt-in (#576): an ideal lens, an aberration-free thin lens of this physical focal length (1–4000 mm) on whatever sensor the
|
|
310
|
+
* frame is (output width × height × pixel pitch; no crop-factor scaling), at any `fNumber` from f/0.5 to f/128
|
|
311
|
+
* (`IDEAL_LENS_LIMITS`; beyond them the request is refused). It replaces the tier lens's focal length and f-number range (the
|
|
312
|
+
* transmission is kept); shutter priority and program may choose any aperture in that range. Recorded in the `lens.ideal`
|
|
313
|
+
* assumption. Its blur is that of `defocus`, `diffraction` and `vignetting`, the same models as with `prime`. It focuses at any
|
|
314
|
+
* distance beyond the focal length (no minimum focus distance; `subjectDistanceM` must be at least twice the focal length, as for
|
|
315
|
+
* every lens). Works in `simulateCapture` and the display view. Not combinable with `prime`, `catalogue` or `focalLengthMm`.
|
|
316
|
+
* Optional `widestFNumber` and `narrowestFNumber` (within `IDEAL_LENS_LIMITS`, widest ≤ narrowest; each defaults to its bound)
|
|
317
|
+
* narrow the range: `fNumber` must lie in it, and shutter priority and program choose apertures only in it. Default off.
|
|
318
|
+
*/
|
|
319
|
+
ideal?: {
|
|
320
|
+
focalLengthMm: number;
|
|
321
|
+
widestFNumber?: number;
|
|
322
|
+
narrowestFNumber?: number;
|
|
323
|
+
};
|
|
302
324
|
/**
|
|
303
325
|
* Opt-in (#549): a lens of the generic lens catalogue by id (`GENERIC_LENS_IDS`, for example "pv-50-1.2-pro" or "pv-50-1.8";
|
|
304
326
|
* docs/LENS_CATALOGUE.md). Like `prime`, its focal length is the full-frame equivalent ÷ the simulated frame's diagonal crop
|
|
@@ -307,9 +329,12 @@ export interface SimulateCaptureOptions {
|
|
|
307
329
|
* colour, at the resolved f-number and focus) through the environment PSF quadrature, its radial distortion, its vignetting
|
|
308
330
|
* and, for the fisheyes, the equisolid projection. Flare, focus breathing and bokeh shape are not rendered. Not combinable
|
|
309
331
|
* with `prime`, `focalLengthMm`, `projection`, `distortion`, `vignetting`, `psf`, `diffraction` or `pupilSampling` (the
|
|
310
|
-
* lens sets them); `defocus` convolves the disc into the lens's PSF.
|
|
332
|
+
* lens sets them); `defocus` convolves the disc into the lens's PSF. The display view renders it with the PSF taken per field zone (#569; docs/DISPLAY_VIEW.md). Default off.
|
|
333
|
+
* #568: the record form `{ kind: <lens id>, fast }` overrides the top-level `fast` flag for this option; the fast method
|
|
334
|
+
* blends the PSF between exactly resolved kernels on a field grid and tabulates each site's expected signal
|
|
335
|
+
* (docs/LENS_CATALOGUE.md, "Fast method").
|
|
311
336
|
*/
|
|
312
|
-
catalogue?: string;
|
|
337
|
+
catalogue?: string | SimulateCaptureLensCatalogueRecord;
|
|
313
338
|
/**
|
|
314
339
|
* Opt-in (#549), with `catalogue` only: a seeded autofocus error (`simulateGenericLensFocusError`). The focus distance becomes
|
|
315
340
|
* the sampled one for (seed, shotIndex), reported in the `lens.focusScatter` assumption; it is visible with `defocus`. Default off.
|
|
@@ -555,7 +580,7 @@ export interface SimulateCaptureOptions {
|
|
|
555
580
|
* Exact or fast (#395). `false` or omitted: every option runs its exact method, the engine reference. `true`: each option
|
|
556
581
|
* listed in {@link SIMULATE_CAPTURE_FAST_OPTIONS} runs its fast method where it has one, a measured, bounded approximation of
|
|
557
582
|
* the exact one; an option without a fast method runs exact. An option's own record `fast` overrides this for that option
|
|
558
|
-
* only. `diagnostics.fast` reports what was requested and what ran.
|
|
583
|
+
* only. `diagnostics.fast` reports what was requested and what ran. `lens.catalogue` has a fast method (#568); the other options run exact.
|
|
559
584
|
*/
|
|
560
585
|
fast?: boolean;
|
|
561
586
|
/** Aborting cancels the running task; the promise rejects with a domain error and returns no partial image. */
|
|
@@ -574,7 +599,7 @@ export type SimulateCaptureCostOption = typeof SIMULATE_CAPTURE_COST_OPTIONS[num
|
|
|
574
599
|
* the record form with `fast` and is reported in `diagnostics.fast.applied` when it runs. Every one of them takes the flag,
|
|
575
600
|
* whether or not it has a fast method yet; one without runs exact and reports `false`.
|
|
576
601
|
*/
|
|
577
|
-
export declare const SIMULATE_CAPTURE_FAST_OPTIONS: readonly ["lens.diffraction", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"];
|
|
602
|
+
export declare const SIMULATE_CAPTURE_FAST_OPTIONS: readonly ["lens.diffraction", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"];
|
|
578
603
|
export type SimulateCaptureFastOption = typeof SIMULATE_CAPTURE_FAST_OPTIONS[number];
|
|
579
604
|
/** `diagnostics.fast` of a capture or display view (#395): what was asked for and what ran. */
|
|
580
605
|
export interface SimulateCaptureFastReport {
|
|
@@ -1107,6 +1132,8 @@ export interface DisplayViewRunContext {
|
|
|
1107
1132
|
* through the lens (display-view-lens-signal.ts, loaded on demand); undefined without them (the rectilinear expected signal).
|
|
1108
1133
|
*/
|
|
1109
1134
|
lens?: ((input: DisplayViewExpectedSignalInput, spanColumns?: number) => DisplayViewExpectedSignal) | undefined;
|
|
1135
|
+
/** #569: with `lens.catalogue`, the field-dependent PSF the lens signal reads (createDisplayViewLensPsf, display-view-lens-signal.ts); its axis kernels set the expected signal's lattice. */
|
|
1136
|
+
lensPsf?: DisplayViewLensPsf | undefined;
|
|
1110
1137
|
diffractionPsf: {
|
|
1111
1138
|
kind: "sampled-local";
|
|
1112
1139
|
configuration: Pick<CalculateSensorPsfIrradianceQuadratureInput, "psf" | "psfWavelengthBasis" | "spatialModel">;
|
|
@@ -82,6 +82,7 @@ import { DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES, siteKernelRuns } from "../o
|
|
|
82
82
|
import { createDefocusBokehSiteKernel, resolveDefocusBokehOptions } from "../optics/defocus-bokeh-kernel.js";
|
|
83
83
|
import { planDefocusBokehZones } from "./defocus-bokeh-zones.js";
|
|
84
84
|
import { resolveGenericPrimeLens } from "../equipment/generic-prime-lenses.js";
|
|
85
|
+
import { IDEAL_LENS_LIMITS } from "../equipment/ideal-lens.js";
|
|
85
86
|
import { calculateChannelElectronsPerRadiance } from "./channel-response.js";
|
|
86
87
|
import { calculateSiteEffectiveSaturationCode } from "../capture/raw-white-level.js";
|
|
87
88
|
import { prepareSensorColorDevelopment } from "../color/sensor-color-development.js";
|
|
@@ -112,7 +113,7 @@ export const SIMULATE_CAPTURE_COST_OPTIONS = /* @__PURE__ */ Object.freeze(["len
|
|
|
112
113
|
* the record form with `fast` and is reported in `diagnostics.fast.applied` when it runs. Every one of them takes the flag,
|
|
113
114
|
* whether or not it has a fast method yet; one without runs exact and reports `false`.
|
|
114
115
|
*/
|
|
115
|
-
export const SIMULATE_CAPTURE_FAST_OPTIONS = /* @__PURE__ */ Object.freeze(["lens.diffraction", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"]);
|
|
116
|
+
export const SIMULATE_CAPTURE_FAST_OPTIONS = /* @__PURE__ */ Object.freeze(["lens.diffraction", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"]);
|
|
116
117
|
export const FACADE_EVIDENCE = /* @__PURE__ */ Object.freeze([/* @__PURE__ */ Object.freeze({ sourceOrigin: "photivra",
|
|
117
118
|
sourceReference: "photivra:simulate-capture-facade", reuseStatus: "photivra-owned" })]);
|
|
118
119
|
const DEFAULTS = /* @__PURE__ */ Object.freeze({ camera: "professional", fNumber: 8, shutterSeconds: 1 / 125, iso: 100, compensationStops: 0,
|
|
@@ -959,35 +960,65 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
959
960
|
const body = preset.body, lens = preset.lens;
|
|
960
961
|
// Lens
|
|
961
962
|
const lensOptions = allowKeys(top.lens, "lens", ["focalLengthMm", "fNumber", "focusDistanceM", "pupilSampling", "projection", "distortion", "vignetting", "psf", "diffraction", "prime", "defocus",
|
|
962
|
-
"catalogue", "focusScatter"]);
|
|
963
|
+
"catalogue", "focusScatter", "ideal"]);
|
|
963
964
|
// Generic prime (2.3): resolved on the simulated frame's size, which the output and sensor sections validate again below.
|
|
964
965
|
let prime;
|
|
966
|
+
// The simulated frame in mm (output width × height × pixel pitch), validated again below.
|
|
967
|
+
const frameMm = () => {
|
|
968
|
+
const o = (typeof top.output === "object" && top.output !== null ? top.output : {}), sn = (typeof top.sensor === "object" && top.sensor !== null ? top.sensor : {});
|
|
969
|
+
const frameW = positive(o.width ?? DEFAULTS.width, "output.width"), frameH = positive(o.height ?? DEFAULTS.height, "output.height"), framePitch = positive(sn.pixelPitchMicrometers ?? DEFAULTS.pixelPitchMicrometers, "sensor.pixelPitchMicrometers");
|
|
970
|
+
return { widthMm: frameW * framePitch / 1000, heightMm: frameH * framePitch / 1000 };
|
|
971
|
+
};
|
|
965
972
|
if (lensOptions.prime !== undefined) {
|
|
966
973
|
if (lensOptions.focalLengthMm !== undefined)
|
|
967
974
|
fail("lens.prime and lens.focalLengthMm cannot both be given: the prime sets the focal length.");
|
|
968
|
-
const
|
|
969
|
-
const frameW = positive(o.width ?? DEFAULTS.width, "output.width"), frameH = positive(o.height ?? DEFAULTS.height, "output.height"), framePitch = positive(sn.pixelPitchMicrometers ?? DEFAULTS.pixelPitchMicrometers, "sensor.pixelPitchMicrometers");
|
|
975
|
+
const activeImagingArea = frameMm();
|
|
970
976
|
if (typeof lensOptions.prime !== "number")
|
|
971
977
|
fail("lens.prime must be a generic prime's full-frame-equivalent focal length in mm.");
|
|
972
|
-
prime = resolveGenericPrimeLens({ equivalentFocalLengthMm: lensOptions.prime, activeImagingArea
|
|
978
|
+
prime = resolveGenericPrimeLens({ equivalentFocalLengthMm: lensOptions.prime, activeImagingArea });
|
|
973
979
|
assume("lens.prime", "model", { lensId: prime.lensId, equivalentFocalLengthMm: prime.equivalentFocalLengthMm, focalLengthMm: prime.focalLengthMm, cropFactor35Mm: prime.cropFactor35Mm,
|
|
974
980
|
widestFNumber: prime.widestFNumber, narrowestFNumber: prime.narrowestFNumber, minimumFocusDistanceM: prime.minimumFocusDistanceM }, "Generic prime (not a real product): focal length = equivalent ÷ the simulated frame's diagonal crop factor, f/" + prime.widestFNumber + "–f/" + prime.narrowestFNumber +
|
|
975
981
|
", minimum focus max(0.10 m, f·(1 + 1/0.15)) (thin-lens approximation). It replaces the tier lens's focal length and f-number range; the tier lens's transmission is kept.");
|
|
976
982
|
}
|
|
983
|
+
// Ideal lens (#576): a physical focal length on any frame, every f-number within IDEAL_LENS_LIMITS.
|
|
984
|
+
let ideal;
|
|
985
|
+
if (lensOptions.ideal !== undefined) {
|
|
986
|
+
for (const key of ["prime", "catalogue", "focalLengthMm"])
|
|
987
|
+
if (lensOptions[key] !== undefined)
|
|
988
|
+
fail("lens.ideal cannot be combined with lens." + key + ".");
|
|
989
|
+
const io = allowKeys(lensOptions.ideal, "lens.ideal", ["focalLengthMm", "widestFNumber", "narrowestFNumber"]), f = io.focalLengthMm, { focalLengthMm: b, fNumber: n } = IDEAL_LENS_LIMITS;
|
|
990
|
+
if (!(typeof f === "number" && f >= b.minimum && f <= b.maximum))
|
|
991
|
+
fail("lens.ideal.focalLengthMm must be " + b.minimum + "–" + b.maximum + " mm (IDEAL_LENS_LIMITS).");
|
|
992
|
+
// #576 (owner, 2026-11-09): an optional f-number range within IDEAL_LENS_LIMITS, for the automatic exposure modes; default the whole range.
|
|
993
|
+
const ranged = io.widestFNumber !== undefined || io.narrowestFNumber !== undefined, lo = io.widestFNumber === undefined ? n.minimum : io.widestFNumber, hi = io.narrowestFNumber === undefined ? n.maximum : io.narrowestFNumber;
|
|
994
|
+
if (!(typeof lo === "number" && typeof hi === "number" && lo >= n.minimum && hi <= n.maximum && lo <= hi))
|
|
995
|
+
fail("lens.ideal.widestFNumber and narrowestFNumber must be f-numbers in f/" + n.minimum + "–f/" + n.maximum + " (IDEAL_LENS_LIMITS), the widest no larger than the narrowest.");
|
|
996
|
+
const lensId = "ideal-" + f + "mm";
|
|
997
|
+
ideal = { id: lensId, name: lensId, focalLengthMm: f, widestFNumber: lo, narrowestFNumber: hi };
|
|
998
|
+
assume("lens.ideal", "model", { lensId, focalLengthMm: f, ...(ranged ? { widestFNumber: lo, narrowestFNumber: hi } : {}) }, "Ideal thin lens without aberrations (not a real product) at this physical focal length" +
|
|
999
|
+
(ranged ? ", f/" + lo + "–f/" + hi : "") + "; the tier lens's transmission is kept.");
|
|
1000
|
+
}
|
|
977
1001
|
// Generic lens catalogue (#549): resolved on the simulated frame like the prime; its character is built once the f-number is known.
|
|
978
1002
|
let catalogue, catalogueFrame;
|
|
979
1003
|
let lensKit;
|
|
1004
|
+
// #568: the shorthand id or the record { kind: <id>, fast }.
|
|
1005
|
+
let catalogueId, catalogueFast;
|
|
1006
|
+
if (lensOptions.catalogue !== undefined && typeof lensOptions.catalogue === "object" && lensOptions.catalogue !== null) {
|
|
1007
|
+
const record = allowKeys(lensOptions.catalogue, "lens.catalogue", ["kind", "fast"]);
|
|
1008
|
+
if (typeof record.kind !== "string")
|
|
1009
|
+
fail("lens.catalogue.kind must be a catalogue lens id (GENERIC_LENS_IDS).");
|
|
1010
|
+
catalogueId = record.kind;
|
|
1011
|
+
catalogueFast = fastOverride(record, "lens.catalogue");
|
|
1012
|
+
}
|
|
1013
|
+
else if (lensOptions.catalogue !== undefined)
|
|
1014
|
+
catalogueId = lensOptions.catalogue;
|
|
980
1015
|
if (lensOptions.catalogue !== undefined) {
|
|
981
1016
|
for (const key of ["prime", "focalLengthMm", "projection", "distortion", "vignetting", "psf", "diffraction", "pupilSampling"])
|
|
982
1017
|
if (lensOptions[key] !== undefined)
|
|
983
1018
|
fail("lens.catalogue cannot be combined with lens." + key + ": the catalogue lens sets its own focal length, projection, distortion, vignetting and PSF (with diffraction).");
|
|
984
|
-
|
|
985
|
-
fail("simulateDisplayView does not support lens.catalogue: the catalogue lens's field-dependent PSF, distortion and vignetting run through the capture's environment quadrature.");
|
|
986
|
-
const o = (typeof top.output === "object" && top.output !== null ? top.output : {}), sn = (typeof top.sensor === "object" && top.sensor !== null ? top.sensor : {});
|
|
987
|
-
const frameW = positive(o.width ?? DEFAULTS.width, "output.width"), frameH = positive(o.height ?? DEFAULTS.height, "output.height"), framePitch = positive(sn.pixelPitchMicrometers ?? DEFAULTS.pixelPitchMicrometers, "sensor.pixelPitchMicrometers");
|
|
988
|
-
catalogueFrame = { widthMm: frameW * framePitch / 1000, heightMm: frameH * framePitch / 1000 };
|
|
1019
|
+
catalogueFrame = frameMm();
|
|
989
1020
|
lensKit = await import("./generic-lens-capture.js");
|
|
990
|
-
catalogue = lensKit.resolveGenericLens({ lensId:
|
|
1021
|
+
catalogue = lensKit.resolveGenericLens({ lensId: catalogueId, activeImagingArea: catalogueFrame });
|
|
991
1022
|
assume("lens.catalogue", "model", { lensId: catalogue.lensId, name: catalogue.name, set: catalogue.set, kind: catalogue.kind, equivalentFocalLengthMm: catalogue.equivalentFocalLengthMm,
|
|
992
1023
|
focalLengthMm: catalogue.focalLengthMm, cropFactor35Mm: catalogue.cropFactor35Mm, widestFNumber: catalogue.widestFNumber, narrowestFNumber: catalogue.narrowestFNumber,
|
|
993
1024
|
minimumFocusDistanceM: catalogue.minimumFocusDistanceM, maximumMagnification: catalogue.maximumMagnification, projection: catalogue.projection, fisheyeImage: catalogue.fisheyeImage }, "Generic catalogue lens " + catalogue.name + " (#549; Photivra's own design, not a real product): focal length = equivalent ÷ the simulated frame's diagonal crop factor, f/" +
|
|
@@ -996,8 +1027,8 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
996
1027
|
}
|
|
997
1028
|
else if (lensOptions.focusScatter !== undefined)
|
|
998
1029
|
fail("lens.focusScatter needs lens.catalogue: the focus scatter is a catalogue lens's own.");
|
|
999
|
-
const lensRange = prime ? { id: prime.lensId, focalLengthMm: prime.focalLengthMm, widestFNumber: prime.widestFNumber, narrowestFNumber: prime.narrowestFNumber } :
|
|
1000
|
-
catalogue ? { id: catalogue.lensId, focalLengthMm: catalogue.focalLengthMm, widestFNumber: catalogue.widestFNumber, narrowestFNumber: catalogue.narrowestFNumber } :
|
|
1030
|
+
const lensRange = prime ? { id: prime.lensId, name: prime.lensId, focalLengthMm: prime.focalLengthMm, widestFNumber: prime.widestFNumber, narrowestFNumber: prime.narrowestFNumber } :
|
|
1031
|
+
catalogue ? { id: catalogue.lensId, name: catalogue.name, focalLengthMm: catalogue.focalLengthMm, widestFNumber: catalogue.widestFNumber, narrowestFNumber: catalogue.narrowestFNumber } : ideal;
|
|
1001
1032
|
const lensExposure = lensRange ? { ...lens.exposure, profileId: "photivra-" + lensRange.id + ":exposure",
|
|
1002
1033
|
focalLengthMmRange: { ...lens.exposure.focalLengthMmRange, value: { minimum: lensRange.focalLengthMm, maximum: lensRange.focalLengthMm } },
|
|
1003
1034
|
aperture: { ...lens.exposure.aperture, widestAvailableFNumber: { kind: "constant", fNumber: { value: lensRange.widestFNumber, evidence: FACADE_EVIDENCE } },
|
|
@@ -1007,8 +1038,9 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1007
1038
|
positive(pick(lensOptions, "focalLengthMm", "lens.focalLengthMm", focalDefault, "The preset lens's focal length (the generic primes are 50 mm)."), "lens.focalLengthMm");
|
|
1008
1039
|
const capabilities = resolveGenericEquipmentExposureCapabilities({ bodyProfile: body.exposure, lensProfile: lensExposure, selectedFocalLengthMm: focalLengthMm });
|
|
1009
1040
|
const subjectDistanceM = positive(pick(top, "subjectDistanceM", "subjectDistanceM", DEFAULTS.subjectDistanceM, "Scene plane 5 m in front of the camera (the generic tiers' reference focus distance)."), "subjectDistanceM");
|
|
1010
|
-
|
|
1011
|
-
|
|
1041
|
+
// #569: twice the focal length itself is admitted (magnification 1, the catalogue Macro's close focus f·(1 + 1/1)).
|
|
1042
|
+
if (subjectDistanceM * 1000 < focalLengthMm * 2)
|
|
1043
|
+
fail("subjectDistanceM must be at least twice the focal length.");
|
|
1012
1044
|
let focusDistanceM = positive(pick(lensOptions, "focusDistanceM", "lens.focusDistanceM", subjectDistanceM, "Focused on the scene plane."), "lens.focusDistanceM");
|
|
1013
1045
|
if (focusDistanceM * 1000 <= focalLengthMm)
|
|
1014
1046
|
fail("lens.focusDistanceM must exceed the focal length.");
|
|
@@ -1294,19 +1326,22 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1294
1326
|
if (demosaicOption !== undefined && demosaicOption !== "bilinear" && demosaicOption !== "ahd")
|
|
1295
1327
|
fail("output.demosaic must be bilinear or ahd.");
|
|
1296
1328
|
const ahd = demosaicOption === "ahd";
|
|
1297
|
-
// #395: exact or fast per option.
|
|
1329
|
+
// #395: exact or fast per option. Only lens.catalogue has a fast method (#568); it reports true below when it ran.
|
|
1298
1330
|
if (top.fast !== undefined && typeof top.fast !== "boolean")
|
|
1299
1331
|
fail("fast must be true or false.");
|
|
1300
1332
|
const fastTop = top.fast === true;
|
|
1301
1333
|
const fastInUse = [["lens.diffraction", diffractionRadiusOption !== undefined, diffractionFast],
|
|
1334
|
+
["lens.catalogue", catalogue !== undefined, catalogueFast],
|
|
1302
1335
|
["sensor.antiAliasing", antiAliasing !== undefined, antiAliasingFast], ["sensor.pixelAperture.box", boxAperture, boxFast], ["output.demosaic.ahd", ahd, demosaicFast]];
|
|
1303
1336
|
const fastApplied = {};
|
|
1304
1337
|
for (const [option, used] of fastInUse)
|
|
1305
1338
|
if (used)
|
|
1306
1339
|
fastApplied[option] = false;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1340
|
+
// #568: the catalogue's fast method, when requested (its record's flag, else the top-level one); whether it runs is decided with the RAW path.
|
|
1341
|
+
const catalogueFastRequested = catalogue !== undefined && (catalogueFast ?? fastTop);
|
|
1342
|
+
let fastReport = Object.freeze({
|
|
1343
|
+
requested: fastTop || [diffractionFast, catalogueFast, antiAliasingFast, boxFast, demosaicFast].some((value) => value === true),
|
|
1344
|
+
applied: Object.freeze({ ...fastApplied }), assurance: "engine-reference"
|
|
1310
1345
|
});
|
|
1311
1346
|
if (borderOption !== undefined && borderOption !== "margin" && borderOption !== "mirror")
|
|
1312
1347
|
fail("output.borderHandling must be margin or mirror.");
|
|
@@ -1518,9 +1553,15 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1518
1553
|
fail("exposure.compensationStops must be within ±10.");
|
|
1519
1554
|
if (exposureOptions.compensationStops === undefined)
|
|
1520
1555
|
assume("exposure.compensationStops", "default", 0, "No exposure compensation.");
|
|
1521
|
-
|
|
1556
|
+
// #569: a lens whose range excludes f/8 (the catalogue's PV 800 9, f/9–f/32; an ideal lens with a narrowed range) takes the same reference
|
|
1557
|
+
// exposure at the aperture of its range nearest f/8, N0 with t = 1/125 s × (N0/8)², which has the same N²/t and so the same reference
|
|
1558
|
+
// luminance; every other request keeps f/8, 1/125 s.
|
|
1559
|
+
const referenceAperture = lensRange ? Math.min(lensRange.narrowestFNumber, Math.max(lensRange.widestFNumber, DEFAULTS.fNumber)) : DEFAULTS.fNumber;
|
|
1560
|
+
const reference = { aperture: referenceAperture, shutterSeconds: referenceAperture === DEFAULTS.fNumber ? DEFAULTS.shutterSeconds :
|
|
1561
|
+
DEFAULTS.shutterSeconds * (referenceAperture / DEFAULTS.fNumber) ** 2, iso: DEFAULTS.iso };
|
|
1522
1562
|
const referenceLuminance = sosReferenceLuminance(reference.aperture, reference.shutterSeconds, reference.iso);
|
|
1523
|
-
assume("exposure.referenceExposure", "model", { ...reference, meteredLuminanceCdPerSquareMeter: referenceLuminance }, "Exposure anchor: f/8, 1/125 s, ISO 100 gives the CIPA DC-004 standard output sensitivity reference exposure H = 10/S = 0.1 lx·s for an average scene luminance of 10·N²/(0.65·t·S) ≈ 1230.77 cd/m² (camera equation H = 0.65·L·t/N²)
|
|
1563
|
+
assume("exposure.referenceExposure", "model", { ...reference, meteredLuminanceCdPerSquareMeter: referenceLuminance }, "Exposure anchor: f/8, 1/125 s, ISO 100 gives the CIPA DC-004 standard output sensitivity reference exposure H = 10/S = 0.1 lx·s for an average scene luminance of 10·N²/(0.65·t·S) ≈ 1230.77 cd/m² (camera equation H = 0.65·L·t/N²)" +
|
|
1564
|
+
(referenceAperture === DEFAULTS.fNumber ? "." : "; the lens excludes f/8: the same N²/t at f/" + referenceAperture + "."));
|
|
1524
1565
|
const meterProfile = body.metering[0].profile, zones = 16, zoneSamples = [];
|
|
1525
1566
|
let luminanceSum = 0;
|
|
1526
1567
|
for (let j = 0; j < zones; j++)
|
|
@@ -1551,12 +1592,13 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1551
1592
|
target = setExposureCompensationOnMeterTarget({ targetId: "facade-target-ec", baseTarget: target, exposureCompensationStops: compensation });
|
|
1552
1593
|
let resolution;
|
|
1553
1594
|
let fNumber, shutterSeconds;
|
|
1595
|
+
const checkFNumber = () => {
|
|
1596
|
+
if (lensRange && (fNumber < lensRange.widestFNumber || fNumber > lensRange.narrowestFNumber))
|
|
1597
|
+
fail("lens.fNumber " + fNumber + " is outside the " + lensRange.name + " range f/" + lensRange.widestFNumber + "–f/" + lensRange.narrowestFNumber + ".");
|
|
1598
|
+
};
|
|
1554
1599
|
if (mode === "manual" || mode === "aperture-priority") {
|
|
1555
1600
|
fNumber = positive(pick(lensOptions, "fNumber", "lens.fNumber", DEFAULTS.fNumber, "f/8."), "lens.fNumber");
|
|
1556
|
-
|
|
1557
|
-
fail("lens.fNumber " + fNumber + " is outside the " + prime.lensId + " range f/" + prime.widestFNumber + "–f/" + prime.narrowestFNumber + ".");
|
|
1558
|
-
if (catalogue && (fNumber < catalogue.widestFNumber || fNumber > catalogue.narrowestFNumber))
|
|
1559
|
-
fail("lens.fNumber " + fNumber + " is outside the " + catalogue.name + " range f/" + catalogue.widestFNumber + "–f/" + catalogue.narrowestFNumber + ".");
|
|
1601
|
+
checkFNumber();
|
|
1560
1602
|
}
|
|
1561
1603
|
else {
|
|
1562
1604
|
if (lensOptions.fNumber !== undefined)
|
|
@@ -1671,11 +1713,10 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1671
1713
|
const exposureGreyCardDeviationStops = callerScene?.kind === "radiance-function" ? null : greyCardDeviationStops(fNumber, shutterSeconds, resolvedIso, illuminanceLux);
|
|
1672
1714
|
// #549: the catalogue lens's character at the resolved f-number and focus: focus scatter, the defocus disc folded into its PSF, then
|
|
1673
1715
|
// its distortion and vignetting profiles over the field the PSF and the queries reach, and its PSF profile.
|
|
1674
|
-
let catalogueTaps, catalogueDefocusApplied = false;
|
|
1716
|
+
let catalogueTaps, catalogueDefocusApplied = false, catalogueKernelRadius = 0;
|
|
1675
1717
|
if (catalogue && lensKit) {
|
|
1676
1718
|
const { GENERIC_LENS_EVIDENCE, genericLensDistortionProfile, genericLensVignettingProfile, resolveGenericLens, simulateGenericLensFocusError, createGenericLensPsfProfile, genericLensPsfKernelRadius, GENERIC_LENS_PSF_MAXIMUM_COMBINED_RADIUS_SITES } = lensKit;
|
|
1677
|
-
|
|
1678
|
-
fail("lens.fNumber " + fNumber + " is outside the " + catalogue.name + " range f/" + catalogue.widestFNumber + "–f/" + catalogue.narrowestFNumber + ".");
|
|
1719
|
+
checkFNumber();
|
|
1679
1720
|
if (lensOptions.focusScatter !== undefined) {
|
|
1680
1721
|
const scatterOptions = allowKeys(lensOptions.focusScatter, "lens.focusScatter", ["seed", "shotIndex"]);
|
|
1681
1722
|
const sample = simulateGenericLensFocusError({ lensId: catalogue.lensId, activeImagingArea: catalogueFrame, fNumber, focusDistanceM, seed: scatterOptions.seed,
|
|
@@ -1743,6 +1784,7 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
1743
1784
|
(built.kernelRadiusSites - built.aberrationKernelRadiusSites) + "), over the limit of " + GENERIC_LENS_PSF_MAXIMUM_COMBINED_RADIUS_SITES + " (" + (2 * built.kernelRadiusSites + 1) ** 2 +
|
|
1744
1785
|
" scene queries per site and spectral node through the environment quadrature). Bring the focus closer to the subject, stop down, or use larger pixels.");
|
|
1745
1786
|
catalogueTaps = (2 * built.kernelRadiusSites + 1) ** 2;
|
|
1787
|
+
catalogueKernelRadius = built.kernelRadiusSites;
|
|
1746
1788
|
psfReport = { aberrationKernelRadiusSites: built.aberrationKernelRadiusSites, kernelRadiusSites: built.kernelRadiusSites, maximumTruncatedEnergyFraction: built.maximumTruncatedEnergyFraction,
|
|
1747
1789
|
fieldNodesMm: { x: built.fieldXmm, y: built.fieldYmm }, wavelengthNodesNm: built.wavelengthsNm, transformSize: built.transformSize, maximumPhaseStepWaves: built.maximumPhaseStepWaves };
|
|
1748
1790
|
}
|
|
@@ -2312,12 +2354,18 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
2312
2354
|
// that only captures does not contain the display view.
|
|
2313
2355
|
// #563: the view's expected signal through the lens, from the capture's own geometry (#548), checked to cover its farthest source point: the outer
|
|
2314
2356
|
// site centre plus the kernel radius and the largest query offset.
|
|
2315
|
-
|
|
2357
|
+
// #569: a catalogue lens's field-dependent PSF, read per field zone by the lens signal (display-view-lens-signal.ts, loaded on demand).
|
|
2358
|
+
const viewLensKit = displayView && lensGeometry ? await import("./display-view-lens-signal.js") : undefined;
|
|
2359
|
+
const displayViewLensPsf = viewLensKit && catalogue && lensPsf ?
|
|
2360
|
+
viewLensKit.createDisplayViewLensPsf(lensPsf.configuration.psf, catalogueKernelRadius, nativeWidth, nativeHeight, pitch) : undefined;
|
|
2361
|
+
if (displayViewLensPsf)
|
|
2362
|
+
assume("view.lensPsf", "model", { method: "piecewise-constant-field-zones", zoneSites: displayViewLensPsf.zoneSites, kernelRadiusSites: catalogueKernelRadius, scientificStatus: "approximation" }, "Each site takes the lens PSF at its field zone's centre, where the capture resolves it at each query point (docs/DISPLAY_VIEW.md, \"Catalogue lenses\").");
|
|
2363
|
+
const lensSignal = viewLensKit ? viewLensKit.prepareDisplayViewLensSignal({ geometry: lensGeometry,
|
|
2316
2364
|
times: rotating ? Array.from({ length: temporalSampleCount }, (_, i) => (i + 0.5) * (shutterSeconds / temporalSampleCount)) : [0], distanceM: subjectDistanceM, metresPerFrameUnit,
|
|
2317
|
-
pitch, imagingArea, boxSamples, spots: antiAliasing ? antiAliasing.components : [{ offsetMicrometers: { x: 0, y: 0 } }] }, nativeWidth, nativeHeight, (diffraction ? diffraction.kernelRadiusSamples : 0) + (displayViewDefocus ? (bokehAxisKernel?.radius ?? defocusDiscKernelRadius(displayViewDefocus.diameterSites)) : 0) +
|
|
2318
|
-
Math.max(0, ...queryOffsetsSites.map((o) => Math.max(Math.abs(o.x), Math.abs(o.y)))) - 0.5, imageSpaceOptions.join(", ")) : undefined;
|
|
2365
|
+
pitch, imagingArea, boxSamples, spots: antiAliasing ? antiAliasing.components : [{ offsetMicrometers: { x: 0, y: 0 } }], psf: displayViewLensPsf, zeroBeyondHorizon: catalogue?.kind === "fisheye" }, nativeWidth, nativeHeight, (diffraction ? diffraction.kernelRadiusSamples : 0) + (displayViewDefocus ? (bokehAxisKernel?.radius ?? defocusDiscKernelRadius(displayViewDefocus.diameterSites)) : 0) + catalogueKernelRadius +
|
|
2366
|
+
Math.max(0, ...queryOffsetsSites.map((o) => Math.max(Math.abs(o.x), Math.abs(o.y)))) - 0.5, catalogue ? "lens.catalogue" : imageSpaceOptions.join(", ")) : undefined;
|
|
2319
2367
|
if (displayView)
|
|
2320
|
-
return await renderView({ lens: lensSignal, ahd, fastReport, antiAliasing, aspect, assume, assumptions, axisChannelResponse, boxSamples, buildProvider, callerScene, chart, materialLayers, chartReport, chartSize, diffraction, defocus: displayViewDefocus, diffractionPsf, fNumber, focalLengthMm, focusDistanceM, height, highlightMode, exposureGain, 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 });
|
|
2368
|
+
return await renderView({ lens: lensSignal, lensPsf: displayViewLensPsf, ahd, fastReport, antiAliasing, aspect, assume, assumptions, axisChannelResponse, boxSamples, buildProvider, callerScene, chart, materialLayers, chartReport, chartSize, diffraction, defocus: displayViewDefocus, diffractionPsf, fNumber, focalLengthMm, focusDistanceM, height, highlightMode, exposureGain, 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 });
|
|
2321
2369
|
// Ideal reference (2.3): the scene without camera effects, at the capture's own exposure, white balance and encoding.
|
|
2322
2370
|
if (idealReference) {
|
|
2323
2371
|
const referenceStarted = clockMs();
|
|
@@ -2439,14 +2487,38 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
2439
2487
|
fail("serializeSimulatedCaptureDngFromParts writes the whole frame: the options take no output.region.");
|
|
2440
2488
|
const dngParts = run === "dng-from-parts" ? viewOption : undefined;
|
|
2441
2489
|
let tabulated;
|
|
2442
|
-
// #
|
|
2443
|
-
|
|
2490
|
+
// #568: lens.catalogue's fast method: tabulated expected electrons with the PSF blended over a field grid (catalogue-lens-signal.ts), for a
|
|
2491
|
+
// still camera, site-centre queries and a chart or material scene; verified per CFA phase against the exact chain within its tolerance.
|
|
2492
|
+
let catalogueFastRun;
|
|
2493
|
+
const catalogueFastEligible = catalogueFastRequested && catalogueTaps !== undefined && lensKit !== undefined && lensPsf !== undefined && dngParts === undefined && tabulatedScene &&
|
|
2494
|
+
!pupil && !rotating && boxSamples === 1 && !antiAliasing && distortion?.kind === "radial" && provider.chartMaterialAt !== undefined && provider.materialRadiance !== undefined;
|
|
2495
|
+
// The tabulated paths' channel response per spectral node and material tables (the fast catalogue path and the expected-signal path).
|
|
2496
|
+
const tabulatedTables = () => {
|
|
2444
2497
|
const { electronsPerRadiance, wavelengths } = axisChannelResponse("simulateCapture");
|
|
2445
|
-
const perNode = CHANNELS.map((c) => Float64Array.from(wavelengths, (nm) => electronsPerRadiance[c].get(nm) ?? 0));
|
|
2446
2498
|
const sceneMaterials = chart ? chart.materials : callerScene.materials, materialRadiance = provider.materialRadiance;
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
...(
|
|
2499
|
+
return { wavelengths, perNode: CHANNELS.map((c) => Float64Array.from(wavelengths, (nm) => electronsPerRadiance[c].get(nm) ?? 0)),
|
|
2500
|
+
materials: { materialAt: provider.chartMaterialAt, whiteRadiance: (nm) => irradianceScale * illuminant.shape.evaluate(nm) / Math.PI,
|
|
2501
|
+
list: [...sceneMaterials.map((m, index) => (m.kind === "neutral" ? { neutral: m.reflectance } : { radiance: (nm) => materialRadiance(index, nm) })),
|
|
2502
|
+
...(chartSize ? [{ neutral: chartSize.surroundReflectance }] : [])] } };
|
|
2503
|
+
};
|
|
2504
|
+
if (catalogueFastEligible) {
|
|
2505
|
+
const { wavelengths, perNode, materials } = tabulatedTables();
|
|
2506
|
+
const { signalInput } = buildExpectedSignalInput({ nativeWidth, nativeHeight, width, wavelengths, perNode, apertureNodes: apertureNodeOffsets(1), boxSamples: 1, antiAliasing: undefined, pitch,
|
|
2507
|
+
materials, radianceAt: provider.radianceAt, imagingArea, focalLengthMm, focusDistanceM, subjectDistanceM, metresPerFrameUnit, caller: "simulateCapture" });
|
|
2508
|
+
const radial = distortion.profile;
|
|
2509
|
+
const signal = lensKit.createCatalogueLensSignal({ nativeWidth, nativeHeight, pitch, psf: lensPsf.configuration.psf, wavelengthsNm: wavelengths, electronsPerRadiance: perNode,
|
|
2510
|
+
scene: signalInput.scene, channelAt: signalInput.channelAt,
|
|
2511
|
+
distortion: { normalizationRadiusMm: radial.normalizationRadiusMm, coefficients: { k1: radial.coefficients.k1, k2: radial.coefficients.k2 ?? 0, k3: radial.coefficients.k3 ?? 0 } },
|
|
2512
|
+
...(vignetting ? { vignetting } : {}), projection: projection === "fisheye-equisolid-angle" ? projection : "rectilinear", outsideImageCircleSeesNothing: catalogue.kind === "fisheye",
|
|
2513
|
+
focalLengthMm, focusDistanceM, distanceM: subjectDistanceM, metresPerFrameUnit });
|
|
2514
|
+
tabulated = await renderTabulatedNativeRaw({ input: environmentRaw, region: regionNative, siteInput, evaluateRadiance: rawProvider().evaluateRadiance,
|
|
2515
|
+
electrons: signal, relativeTolerance: lensKit.CATALOGUE_FAST_VERIFICATION_TOLERANCE, onTile: rawTick, tileWidth, yieldControl: () => yieldControl(), checkAbort });
|
|
2516
|
+
if (tabulated)
|
|
2517
|
+
catalogueFastRun = { ...signal.grid, verificationRelativeDifference: tabulated.verificationRelativeDifference };
|
|
2518
|
+
}
|
|
2519
|
+
// #553: a cat's eye varies the kernel over the field, which the expected-signal tables (one kernel per node) do not take.
|
|
2520
|
+
else if (dngParts === undefined && tabulatedScene && !pupil && !lensFieldMapping && !vignetting && !lensPsf && !rotating && !bokehZones && (!diffraction || diffractionObjectSpace) && provider.chartMaterialAt && provider.materialRadiance) {
|
|
2521
|
+
const { wavelengths, perNode, materials: tabulatedMaterials } = tabulatedTables();
|
|
2450
2522
|
const { signalInput: builtSignalInput } = buildExpectedSignalInput({ nativeWidth, nativeHeight, width, wavelengths, perNode, apertureNodes: apertureNodeOffsets(boxSamples), boxSamples, antiAliasing, pitch,
|
|
2451
2523
|
diffraction: diffraction && !defocusSiteKernels ? { radius: diffraction.kernelRadiusSamples, psf: diffractionPsf.configuration.psf, profileWavelengths: diffraction.profile.axes.wavelengthNm } : undefined,
|
|
2452
2524
|
materials: tabulatedMaterials, radianceAt: provider.radianceAt, imagingArea, focalLengthMm, focusDistanceM, subjectDistanceM, metresPerFrameUnit, caller: "simulateCapture" });
|
|
@@ -2474,6 +2546,13 @@ export async function runCapture(options, run, viewOption, renderView) {
|
|
|
2474
2546
|
let rawOutput;
|
|
2475
2547
|
if (dngParts !== undefined)
|
|
2476
2548
|
rawOutput = { raw: joinNativeRawParts(dngParts, plan, nativeWidth, nativeHeight), providerEvaluationCount: 0 };
|
|
2549
|
+
else if (tabulated && catalogueFastRun) {
|
|
2550
|
+
rawOutput = { raw: tabulated.raw, providerEvaluationCount: tabulated.providerEvaluationCount };
|
|
2551
|
+
fastApplied["lens.catalogue"] = true;
|
|
2552
|
+
fastReport = Object.freeze({ ...fastReport, applied: Object.freeze({ ...fastApplied }), assurance: "approximation-with-a-priori-bound" });
|
|
2553
|
+
for (const a of lensKit.catalogueFastAssumptions(catalogueFastRun))
|
|
2554
|
+
assume(a.id, "model", a.value, a.explanation);
|
|
2555
|
+
}
|
|
2477
2556
|
else if (tabulated) {
|
|
2478
2557
|
rawOutput = { raw: tabulated.raw, providerEvaluationCount: tabulated.providerEvaluationCount };
|
|
2479
2558
|
// The verification's relative difference itself is not reported: it is a last-bits quantity, and captures use the JavaScript
|