@photivra/engine 2.0.0 → 2.2.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/capture/export-jpeg.js +7 -1
  6. package/dist/capture/export-jpeg.js.map +1 -1
  7. package/dist/capture/native-environment-lean.js +5 -2
  8. package/dist/capture/native-environment-lean.js.map +1 -1
  9. package/dist/capture/native-environment-raw.js +2 -2
  10. package/dist/capture/native-environment-raw.js.map +1 -1
  11. package/dist/capture/native-raw-development.js +1 -1
  12. package/dist/capture/native-raw-development.js.map +1 -1
  13. package/dist/capture/native-raw.d.ts +16 -0
  14. package/dist/capture/native-raw.js +16 -3
  15. package/dist/capture/native-raw.js.map +1 -1
  16. package/dist/capture/packed-sensor-raw.d.ts +7 -0
  17. package/dist/capture/packed-sensor-raw.js +6 -2
  18. package/dist/capture/packed-sensor-raw.js.map +1 -1
  19. package/dist/core/pixel-limits.d.ts +14 -0
  20. package/dist/core/pixel-limits.js +40 -0
  21. package/dist/core/pixel-limits.js.map +1 -0
  22. package/dist/core/version.d.ts +1 -1
  23. package/dist/core/version.js +1 -1
  24. package/dist/core/version.js.map +1 -1
  25. package/dist/facade/chart-radiance-batch.d.ts +7 -0
  26. package/dist/facade/chart-radiance-batch.js +7 -3
  27. package/dist/facade/chart-radiance-batch.js.map +1 -1
  28. package/dist/facade/diffraction-provider.d.ts +30 -0
  29. package/dist/facade/diffraction-provider.js +127 -0
  30. package/dist/facade/diffraction-provider.js.map +1 -0
  31. package/dist/facade/host-yield.d.ts +1 -1
  32. package/dist/facade/host-yield.js +0 -4
  33. package/dist/facade/host-yield.js.map +1 -1
  34. package/dist/facade/simulate-capture.d.ts +88 -2
  35. package/dist/facade/simulate-capture.js +343 -41
  36. package/dist/facade/simulate-capture.js.map +1 -1
  37. package/dist/index.d.ts +5 -2
  38. package/dist/index.js +4 -2
  39. package/dist/index.js.map +1 -1
  40. package/dist/motion/temporal-exposure-sum.d.ts +7 -0
  41. package/dist/motion/temporal-exposure-sum.js +6 -2
  42. package/dist/motion/temporal-exposure-sum.js.map +1 -1
  43. package/dist/optics/diffraction-psf-profile.d.ts +118 -0
  44. package/dist/optics/diffraction-psf-profile.js +252 -0
  45. package/dist/optics/diffraction-psf-profile.js.map +1 -0
  46. package/dist/optics/irradiance-convolution.d.ts +7 -0
  47. package/dist/optics/irradiance-convolution.js +6 -2
  48. package/dist/optics/irradiance-convolution.js.map +1 -1
  49. package/dist/output/native-capture-sdr.d.ts +7 -0
  50. package/dist/output/native-capture-sdr.js +6 -3
  51. package/dist/output/native-capture-sdr.js.map +1 -1
  52. package/dist/output/png-encoder.d.ts +10 -1
  53. package/dist/output/png-encoder.js +10 -4
  54. package/dist/output/png-encoder.js.map +1 -1
  55. package/dist/output/print-jpeg.d.ts +18 -0
  56. package/dist/output/print-jpeg.js +15 -4
  57. package/dist/output/print-jpeg.js.map +1 -1
  58. package/dist/sensor/sampling-aperture.d.ts +15 -0
  59. package/dist/sensor/sampling-aperture.js +31 -10
  60. package/dist/sensor/sampling-aperture.js.map +1 -1
  61. package/docs/API_REFERENCE.md +27 -1
  62. package/docs/QUICK_START.md +285 -13
  63. package/docs/USAGE.md +3 -1
  64. package/docs/api/capture-native-raw.md +11 -2
  65. package/docs/api/capture-packed-sensor-raw.md +7 -1
  66. package/docs/api/core-pixel-limits.md +19 -0
  67. package/docs/api/core-version.md +1 -1
  68. package/docs/api/exports.json +62 -2
  69. package/docs/api/facade-chart-radiance-batch.md +8 -2
  70. package/docs/api/facade-simulate-capture.md +83 -3
  71. package/docs/api/motion-temporal-exposure-sum.md +7 -1
  72. package/docs/api/optics-diffraction-psf-profile.md +96 -0
  73. package/docs/api/optics-irradiance-convolution.md +7 -1
  74. package/docs/api/output-native-capture-sdr.md +7 -2
  75. package/docs/api/output-png-encoder.md +8 -1
  76. package/docs/api/output-print-jpeg.md +17 -0
  77. package/package.json +3 -2
@@ -2,10 +2,6 @@
2
2
  /** Returns a function that yields to the host event loop once per call, and the mechanism it uses. */
3
3
  export function createHostYield(host = globalThis) {
4
4
  const g = host;
5
- const scheduler = g.scheduler;
6
- if (scheduler !== undefined && typeof scheduler.yield === "function") {
7
- return { mechanism: "scheduler.yield", yieldControl: async () => { await scheduler.yield.call(scheduler); } };
8
- }
9
5
  const Channel = g.MessageChannel;
10
6
  if (typeof Channel === "function") {
11
7
  return { mechanism: "message-channel", yieldControl: () => new Promise((resolve) => {
@@ -1 +1 @@
1
- {"version":3,"file":"host-yield.js","sourceRoot":"","sources":["../../src/facade/host-yield.ts"],"names":[],"mappings":"AAAA,sCAAsC;AA2BtC,sGAAsG;AACtG,MAAM,UAAU,eAAe,CAAC,OAAe,UAAU;IACvD,MAAM,CAAC,GAAG,IAAoB,CAAC;IAC/B,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;IAC9B,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACrE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,YAAY,EAAE,KAAK,IAAmB,EAAE,GAAG,MAAM,SAAS,CAAC,KAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChI,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,CAAC,cAAc,CAAC;IACjC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;QAClC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBACvF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC9B,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,GAAS,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC5E,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YACvC,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,CAAC,YAAY,CAAC;IACjC,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChI,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACvH,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Host yield for the capture facade's cooperative tasks. Browsers clamp nested `setTimeout(…, 0)` to\n * at least 4 ms (WHATWG HTML timers, nesting level above 5), so a task that yields every few\n * milliseconds through timers can sit idle for a large share of its run time. These yields are not\n * clamped. The first available mechanism is used:\n *\n * 1. `scheduler.yield()` (Prioritized Task Scheduling API), which also keeps the task's priority;\n * 2. a `MessageChannel` message, a new task without the timer clamp (one channel per yield, closed after\n * delivery, so no port keeps a Node process alive);\n * 3. `setImmediate` (Node and some other non-browser hosts);\n * 4. `setTimeout(…, 0)` as the last resort.\n *\n * Only global feature detection is used; nothing is imported, so the browser package surface is unchanged.\n * The yield changes scheduling only, never results.\n */\n\ntype YieldGlobals = {\n scheduler?: { yield?: () => Promise<unknown> };\n MessageChannel?: new () => { port1: { onmessage: ((event: unknown) => void) | null; close(): void }; port2: { postMessage(message: unknown): void } };\n setImmediate?: (callback: () => void) => unknown;\n};\n\n/** Mechanism chosen by {@link createHostYield}, for diagnostics and tests. */\nexport type HostYieldMechanism = \"scheduler.yield\" | \"message-channel\" | \"set-immediate\" | \"set-timeout\";\n\n/** Returns a function that yields to the host event loop once per call, and the mechanism it uses. */\nexport function createHostYield(host: object = globalThis): { mechanism: HostYieldMechanism; yieldControl: () => Promise<void> } {\n const g = host as YieldGlobals;\n const scheduler = g.scheduler;\n if (scheduler !== undefined && typeof scheduler.yield === \"function\") {\n return { mechanism: \"scheduler.yield\", yieldControl: async (): Promise<void> => { await scheduler.yield!.call(scheduler); } };\n }\n const Channel = g.MessageChannel;\n if (typeof Channel === \"function\") {\n return { mechanism: \"message-channel\", yieldControl: () => new Promise<void>((resolve) => {\n const channel = new Channel();\n channel.port1.onmessage = (): void => { channel.port1.close(); resolve(); };\n channel.port2.postMessage(undefined);\n }) };\n }\n const immediate = g.setImmediate;\n if (typeof immediate === \"function\") {\n return { mechanism: \"set-immediate\", yieldControl: () => new Promise<void>((resolve) => { immediate.call(host, resolve); }) };\n }\n return { mechanism: \"set-timeout\", yieldControl: () => new Promise<void>((resolve) => { setTimeout(resolve, 0); }) };\n}\n"]}
1
+ {"version":3,"file":"host-yield.js","sourceRoot":"","sources":["../../src/facade/host-yield.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAsCtC,sGAAsG;AACtG,MAAM,UAAU,eAAe,CAAC,OAAe,UAAU;IACvD,MAAM,CAAC,GAAG,IAAoB,CAAC;IAC/B,MAAM,OAAO,GAAG,CAAC,CAAC,cAAc,CAAC;IACjC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;QAClC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBACvF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC9B,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,GAAS,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC5E,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YACvC,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,CAAC,YAAY,CAAC;IACjC,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChI,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACvH,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Host yield for the capture facade's cooperative tasks. Browsers clamp nested `setTimeout(…, 0)` to\n * at least 4 ms (WHATWG HTML timers, nesting level above 5), so a task that yields every few\n * milliseconds through timers can sit idle for a large share of its run time. The first available\n * mechanism is used:\n *\n * 1. a `MessageChannel` message, a new task without the timer clamp (one channel per yield, closed after\n * delivery, so no port keeps a Node process alive);\n * 2. `setImmediate` (Node and some other non-browser hosts);\n * 3. `setTimeout(…, 0)` as the last resort.\n *\n * Every yield must let tasks that are already queued run first, so that a caller's cancel message (for\n * example a Web Worker `message` event that aborts the capture's `AbortSignal`) is handled at the next\n * yield. A `MessageChannel` message is queued on the HTML posted message task source, the same source as\n * worker and port `message` events, and tasks from one source run in the order they were queued.\n *\n * `scheduler.yield()` is deliberately not used (engine 2.0.0 used it first). Its continuation inherits\n * the \"user-visible\" priority and gets a higher effective priority than ordinary tasks; the WICG\n * Prioritized Task Scheduling draft (§ \"Task and Continuation Priorities\", effective priority table;\n * § \"Event loop: processing model\" note) intends such continuations to run ahead of most other task\n * sources, and its explainer (\"Preventing Task Starvation by Continuations\") only bounds that by\n * duration. A capture that yields every 8 ms through `scheduler.yield()` therefore starved a worker's\n * cancel message until the capture finished (measured in Chromium).\n *\n * Only global feature detection is used; nothing is imported, so the browser package surface is unchanged.\n * The yield changes scheduling only, never results.\n */\n\ntype YieldGlobals = {\n MessageChannel?: new () => { port1: { onmessage: ((event: unknown) => void) | null; close(): void }; port2: { postMessage(message: unknown): void } };\n setImmediate?: (callback: () => void) => unknown;\n};\n\n/** Mechanism chosen by {@link createHostYield}, for diagnostics and tests. */\nexport type HostYieldMechanism = \"message-channel\" | \"set-immediate\" | \"set-timeout\";\n\n/** Returns a function that yields to the host event loop once per call, and the mechanism it uses. */\nexport function createHostYield(host: object = globalThis): { mechanism: HostYieldMechanism; yieldControl: () => Promise<void> } {\n const g = host as YieldGlobals;\n const Channel = g.MessageChannel;\n if (typeof Channel === \"function\") {\n return { mechanism: \"message-channel\", yieldControl: () => new Promise<void>((resolve) => {\n const channel = new Channel();\n channel.port1.onmessage = (): void => { channel.port1.close(); resolve(); };\n channel.port2.postMessage(undefined);\n }) };\n }\n const immediate = g.setImmediate;\n if (typeof immediate === \"function\") {\n return { mechanism: \"set-immediate\", yieldControl: () => new Promise<void>((resolve) => { immediate.call(host, resolve); }) };\n }\n return { mechanism: \"set-timeout\", yieldControl: () => new Promise<void>((resolve) => { setTimeout(resolve, 0); }) };\n}\n"]}
@@ -12,6 +12,8 @@ import type { RadialDistortionProfile } from "../optics/radial-distortion.js";
12
12
  import type { BrownConradyDistortionProfile } from "../optics/brown-conrady-distortion.js";
13
13
  import type { IlluminationVignettingProfile } from "../optics/illumination-vignetting.js";
14
14
  import type { CalculateEnvironmentSensorPhotoSignalInput } from "../sensor/environment-photo-signal.js";
15
+ import { type DiffractionLimitedPsfProfile } from "../optics/diffraction-psf-profile.js";
16
+ import { type AntiAliasingPointSplitComponent } from "../sensor/optical-stack.js";
15
17
  import { type ChartScene } from "./chart-scenes.js";
16
18
  import { type CaptureIlluminant, type CallerRadianceScene, type CallerReflectanceScene } from "./scene-radiance.js";
17
19
  /** Version of how the facade builds requests. It changes whenever the same input would build a different request. */
@@ -38,8 +40,12 @@ export type SimulateCaptureWhiteBalance = "scene-illuminant" | "auto" | "none" |
38
40
  * PQ and HLG (BT.2100) are not part of 2.0; see THIRD_PARTY.md.
39
41
  */
40
42
  export type SimulateCaptureColorEncoding = "srgb" | "display-p3" | "rec2020";
41
- /** Built-in Bayer demosaic kernels (`createBuiltInBayerDemosaicProfiles`). */
42
- export type SimulateCaptureDemosaic = "bilinear";
43
+ /**
44
+ * Demosaic. `bilinear`: the built-in Bayer kernels (`createBuiltInBayerDemosaicProfiles`). `ahd` (2.1): adaptive
45
+ * homogeneity-directed demosaicing (`createAhdDemosaicTask`, after Hirakawa & Parks 2005), a non-linear
46
+ * edge-directed method that reduces zipper and false colour on sharp edges.
47
+ */
48
+ export type SimulateCaptureDemosaic = "bilinear" | "ahd";
43
49
  /**
44
50
  * `margin` renders extra native sites around the output, as far as the kernel reaches. `mirror` renders
45
51
  * exactly the output and reads mirrored (reflect-101) copies of the missing border sites. Mirroring is
@@ -53,6 +59,32 @@ export type SimulateCaptureLensDistortion = {
53
59
  kind: "brown-conrady";
54
60
  profile: BrownConradyDistortionProfile;
55
61
  };
62
+ /** Diffraction model (2.1): the shorthand or a record with an explicit kernel radius in sites. */
63
+ export type SimulateCaptureLensDiffraction = "circular-aperture" | {
64
+ aperture: "circular-aperture";
65
+ kernelRadiusSamples?: number;
66
+ };
67
+ /**
68
+ * Optical anti-aliasing (low-pass) filter (2.1), modelled as birefringent point splitting: each image point is
69
+ * split into equally bright copies (the engine's `normalized-point-splitting-kernel` optical-stack response).
70
+ * - `four-spot`: two crossed birefringent plates; copies at (±d/2, ±d/2), weight 1/4 each, MTF |cos(π·f_x·d)·cos(π·f_y·d)|.
71
+ * - `two-spot`: one plate splitting along `axis`; copies at ±d/2, weight 1/2 each, MTF |cos(π·f·d)|.
72
+ * - `point-splitting`: the engine contract itself, offsets in micrometres (+x right, +y down) and positive weights
73
+ * summing to 1 (at most 16 components, each within 4 sites).
74
+ * d = `separationSites` × pixel pitch, 0 < separationSites ≤ 4, default 1 (first MTF zero at the sensor's Nyquist
75
+ * frequency 1/(2·pitch)).
76
+ */
77
+ export type SimulateCaptureSensorAntiAliasing = {
78
+ kind: "four-spot";
79
+ separationSites?: number;
80
+ } | {
81
+ kind: "two-spot";
82
+ axis: "horizontal" | "vertical";
83
+ separationSites?: number;
84
+ } | {
85
+ kind: "point-splitting";
86
+ components: readonly AntiAliasingPointSplitComponent[];
87
+ };
56
88
  export interface SimulateCaptureMotion {
57
89
  /** Constant camera angular velocity, rad/s, in the engine's pitch/yaw/roll convention. Missing axes are 0. */
58
90
  angularVelocityRadPerSec?: {
@@ -93,6 +125,13 @@ export interface SimulateCaptureOptions {
93
125
  psf?: Extract<CalculateEnvironmentSensorPhotoSignalInput["psf"], {
94
126
  kind: "sampled-local";
95
127
  }>;
128
+ /**
129
+ * Opt-in (2.1) diffraction blur of an ideal, aberration-free, in-focus lens with a circular pupil at the
130
+ * capture's resolved f-number: the engine builds the Airy kernels (`createDiffractionLimitedPsfProfile`)
131
+ * on the site pitch. `kernelRadiusSamples` (0–32) sets the kernel window; default: the third dark ring at
132
+ * the longest spectral node. Not combinable with `psf` or `pupilSampling`. Default off.
133
+ */
134
+ diffraction?: SimulateCaptureLensDiffraction;
96
135
  };
97
136
  exposure?: {
98
137
  /** Default "manual". */
@@ -145,6 +184,13 @@ export interface SimulateCaptureOptions {
145
184
  dng?: {
146
185
  capturedAtUtc?: string;
147
186
  };
187
+ /**
188
+ * Opt-in large images: the most native sensor sites (the output plus the demosaic margin on each side) the
189
+ * capture may allocate, an integer up to `NATIVE_RAW_LIMITS.ceilingPixels` (2^27). With it, `width` and
190
+ * `height` may each reach 16 384 sites including the margin; without it they stay at most 4094. Time and
191
+ * memory grow with the pixel count (see "Large images" in the quick start); browsers may fail at large sizes.
192
+ */
193
+ maximumPixels?: number;
148
194
  };
149
195
  /** Camera motion and shutter type. Default: still camera, global electronic shutter. */
150
196
  motion?: SimulateCaptureMotion;
@@ -157,12 +203,23 @@ export interface SimulateCaptureOptions {
157
203
  temporalSampleCount?: number;
158
204
  /** Noise seed (uint32). Default 1. */
159
205
  noiseSeed?: number;
206
+ /**
207
+ * Opt-in (2.1) optical anti-aliasing filter (birefringent point splitting, `SimulateCaptureSensorAntiAliasing`).
208
+ * Multiplies the scene queries by the number of split points. Default off (the tiers declare no filter).
209
+ */
210
+ antiAliasing?: SimulateCaptureSensorAntiAliasing;
160
211
  };
161
212
  /** Aborting cancels the running task; the promise rejects with a domain error and returns no partial image. */
162
213
  signal?: AbortSignal;
163
214
  /** Called after each committed RAW, development or JPEG tile. Cannot change output. */
164
215
  onProgress?: (progress: SimulateCaptureProgress) => void;
165
216
  }
217
+ /**
218
+ * Options reported in `diagnostics.options` (2.1), in this order, when they ran: the ones that change how much work
219
+ * a capture does. `motion.rotation` is a non-zero angular velocity; `sensor.temporalSampleCount` a count above 1.
220
+ */
221
+ export declare const SIMULATE_CAPTURE_COST_OPTIONS: readonly ["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf", "lens.diffraction", "sensor.antiAliasing", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual", "output.format.jpeg", "output.format.png", "output.format.dng"];
222
+ export type SimulateCaptureCostOption = typeof SIMULATE_CAPTURE_COST_OPTIONS[number];
166
223
  export interface SimulateCaptureProgress {
167
224
  stage: "raw" | "white-balance" | "development" | "jpeg";
168
225
  completedTileCount: number;
@@ -204,6 +261,19 @@ export interface SimulateCaptureResult {
204
261
  siteInput(nativeIndex: number): SimulateEnvironmentSensorRawFrameInput["sites"][number];
205
262
  development: Omit<NativeRawDevelopmentInput, "raw">;
206
263
  whiteBalanceState: ResolvedWhiteBalanceState | null;
264
+ /**
265
+ * Present with lens.diffraction (2.1): the engine-built Airy profile, the equivalent environment sampled PSF
266
+ * selection, and how it was applied. With `object-space-convolution` the site inputs declare no PSF and the
267
+ * provider answers with the PSF-convolved radiance; putting `psf` into each site input and using the plain
268
+ * scene provider gives the same expected signal (to float rounding) through the per-tap environment quadrature.
269
+ */
270
+ diffraction?: {
271
+ profile: DiffractionLimitedPsfProfile;
272
+ psf: Extract<CalculateEnvironmentSensorPhotoSignalInput["psf"], {
273
+ kind: "sampled-local";
274
+ }>;
275
+ execution: "object-space-convolution" | "environment-psf-quadrature";
276
+ };
207
277
  };
208
278
  exposure: {
209
279
  mode: SimulateCaptureExposureMode;
@@ -250,13 +320,29 @@ export interface SimulateCaptureResult {
250
320
  adjustedPixelCount: number;
251
321
  lightnessClippedPixelCount: number;
252
322
  };
323
+ /**
324
+ * Wall-clock milliseconds per stage. `raw`, `whiteBalance` (the second RAW pass of auto white balance), `development`
325
+ * (includes DNG writing), `jpeg` and `total` are whole milliseconds (`Date.now()`). Since 2.1, where a stage ran:
326
+ * `diffraction` (the object-space convolution of `lens.diffraction`; part of `raw` and `whiteBalance`), `demosaic`
327
+ * (the AHD demosaic; part of `development`) and `encode` (PNG, JPEG or DNG serialization), with sub-millisecond
328
+ * resolution where the host has `performance.now()`. Timings vary from run to run and never enter any digest or output byte.
329
+ */
253
330
  timingsMs: {
254
331
  raw: number;
255
332
  whiteBalance: number;
256
333
  development: number;
257
334
  jpeg: number;
258
335
  total: number;
336
+ diffraction?: number;
337
+ demosaic?: number;
338
+ encode?: number;
259
339
  };
340
+ /**
341
+ * 2.1: the opt-in options that changed the work of this run, in a fixed order (see `SIMULATE_CAPTURE_COST_OPTIONS`).
342
+ * Empty for a capture with every default. With `timingsMs` it lets an application measure each option's cost on
343
+ * the user's device.
344
+ */
345
+ options: readonly SimulateCaptureCostOption[];
260
346
  };
261
347
  provenance: {
262
348
  scientificStatus: "approximation";