@photivra/engine 2.7.0 → 2.8.1

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 (49) hide show
  1. package/CHANGELOG.md +114 -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 +25 -12
  6. package/dist/capture/export-jpeg.js.map +1 -1
  7. package/dist/capture/native-environment-lean.d.ts +35 -5
  8. package/dist/capture/native-environment-lean.js +175 -16
  9. package/dist/capture/native-environment-lean.js.map +1 -1
  10. package/dist/capture/native-environment-raw.d.ts +9 -1
  11. package/dist/capture/native-environment-raw.js +14 -9
  12. package/dist/capture/native-environment-raw.js.map +1 -1
  13. package/dist/capture/native-environment-tabulated.d.ts +58 -0
  14. package/dist/capture/native-environment-tabulated.js +91 -0
  15. package/dist/capture/native-environment-tabulated.js.map +1 -0
  16. package/dist/core/internal-radiance-evaluator.d.ts +28 -0
  17. package/dist/core/internal-radiance-evaluator.js +30 -0
  18. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  19. package/dist/core/version.d.ts +1 -1
  20. package/dist/core/version.js +1 -1
  21. package/dist/core/version.js.map +1 -1
  22. package/dist/facade/diffraction-provider.js +33 -12
  23. package/dist/facade/diffraction-provider.js.map +1 -1
  24. package/dist/facade/display-view.d.ts +14 -83
  25. package/dist/facade/display-view.js +331 -846
  26. package/dist/facade/display-view.js.map +1 -1
  27. package/dist/facade/expected-signal-setup.d.ts +65 -0
  28. package/dist/facade/expected-signal-setup.js +52 -0
  29. package/dist/facade/expected-signal-setup.js.map +1 -0
  30. package/dist/facade/expected-signal.d.ts +83 -0
  31. package/dist/facade/expected-signal.js +571 -0
  32. package/dist/facade/expected-signal.js.map +1 -0
  33. package/dist/facade/scene-radiance.d.ts +6 -0
  34. package/dist/facade/scene-radiance.js +31 -10
  35. package/dist/facade/scene-radiance.js.map +1 -1
  36. package/dist/facade/simulate-capture.js +109 -28
  37. package/dist/facade/simulate-capture.js.map +1 -1
  38. package/dist/facade/simulate-display-view.js +26 -49
  39. package/dist/facade/simulate-display-view.js.map +1 -1
  40. package/dist/optics/site-diffraction-kernel.d.ts +5 -0
  41. package/dist/optics/site-diffraction-kernel.js +32 -0
  42. package/dist/optics/site-diffraction-kernel.js.map +1 -0
  43. package/dist/output/print-jpeg.js +70 -42
  44. package/dist/output/print-jpeg.js.map +1 -1
  45. package/docs/API_REFERENCE.md +1 -1
  46. package/docs/QUICK_START.md +1 -1
  47. package/docs/api/core-version.md +1 -1
  48. package/docs/api/exports.json +2 -2
  49. package/package.json +1 -1
@@ -28,7 +28,7 @@
28
28
  * without it every encoded file is byte-identical to 2.3.
29
29
  * @see docs/QUICK_START.md
30
30
  */
31
- import { markInternalSceneRadianceEvaluator } from "../core/internal-radiance-evaluator.js";
31
+ import { markInternalNativeTileReader, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from "../core/internal-radiance-evaluator.js";
32
32
  import { InvalidConfigurationError } from "../core/configuration-error.js";
33
33
  import { requireInputObject } from "../core/record-validation.js";
34
34
  import { ENGINE_API_VERSION } from "../core/version.js";
@@ -47,6 +47,9 @@ import { createColorTemperatureWhiteBalanceIntent, resolveColorTemperatureWhiteB
47
47
  import { createCaptureWhiteBalanceIntent } from "../capture/simulated-capture.js";
48
48
  import { SENSOR_RAW_PRODUCER_NOISE_MODEL } from "../capture/sensor-raw-producer.js";
49
49
  import { calculateNativeRawPlan, nativeRawMaximumProviderEvaluations, NATIVE_RAW_LIMITS } from "../capture/native-raw.js";
50
+ import { renderTabulatedNativeRaw, TABULATED_RELATIVE_TOLERANCE } from "../capture/native-environment-tabulated.js";
51
+ import { createDisplayViewExpectedSignal } from "./expected-signal.js";
52
+ import { buildExpectedSignalInput } from "./expected-signal-setup.js";
50
53
  import { createNativeEnvironmentRawTask, createNativeEnvironmentRawRegionTask } from "../capture/native-environment-raw.js";
51
54
  import { createNativeRawDevelopmentTask } from "../capture/native-raw-development.js";
52
55
  import { parseSdrRenderingProfile } from "../output/sdr-rendering.js";
@@ -71,7 +74,7 @@ import { calculateSensorSpectralQuadrature } from "../sensor/spectral-quadrature
71
74
  import { createDiffractionLimitedPsfProfile } from "../optics/diffraction-psf-profile.js";
72
75
  import { createDiffractedPlanarProvider } from "./diffraction-provider.js";
73
76
  import { resolveGenericPrimeLens } from "../equipment/generic-prime-lenses.js";
74
- import { calculateEnvironmentSensorPhotoSignal } from "../sensor/environment-photo-signal.js";
77
+ import { executeEnvironmentSensorPhotoSignal, planEnvironmentSensorPhotoSignal } from "../sensor/environment-photo-signal.js";
75
78
  import { calculateSiteEffectiveSaturationCode } from "../capture/raw-white-level.js";
76
79
  import { prepareSensorColorDevelopment } from "../color/sensor-color-development.js";
77
80
  import { resolveAntiAliasingSpatialKernel } from "../sensor/optical-stack.js";
@@ -80,7 +83,7 @@ import { prepareRgbGamutMapping } from "../color/gamut-mapping.js";
80
83
  import { serializeNativeRawDng } from "../capture/native-raw-dng.js";
81
84
  import { parseSensorReadoutConversionProfile, resolveSensorReadoutRegime } from "../sensor/raw-readout.js";
82
85
  import { resolveChartScene, chartMaterialInputs, CHART_SCENE_PRESETS } from "./chart-scenes.js";
83
- import { resolveCaptureIlluminant, calculateIlluminantIrradianceScale, buildPlanarSceneRadianceProvider, requireCallerScene, FACADE_SPECTRAL_RANGE_NANOMETERS, resolveChartSizeOption, PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W } from "./scene-radiance.js";
86
+ import { resolveCaptureIlluminant, calculateIlluminantIrradianceScale, buildPlanarSceneRadianceProvider, requireCallerScene, FACADE_SPECTRAL_RANGE_NANOMETERS, resolveChartSizeOption, PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W, planarProviderMaterialOfDirection } from "./scene-radiance.js";
84
87
  /** Version of how the facade builds requests. It changes whenever the same input would build a different request. */
85
88
  export const SIMULATE_CAPTURE_FACADE_VERSION = "1.0.0";
86
89
  /** Default midpoint samples per axis of `sensor.pixelAperture: "box"`. */
@@ -139,6 +142,9 @@ const AHD_VALUE_REACH = 3;
139
142
  /** Anti-aliasing filter limits: split points, and the largest offset in sites (bounds the scene work and the convolution margin). */
140
143
  const AA_MAXIMUM_COMPONENTS = 16;
141
144
  const AA_MAXIMUM_OFFSET_SITES = 4;
145
+ /** Channel responses kept across calls (`axisChannelResponse`): one entry per distinct sensor, exposure and optics state. */
146
+ const CHANNEL_RESPONSE_CACHE_SIZE = 8;
147
+ const channelResponseCache = new Map();
142
148
  /** Default birefringent separation in sites: the two-spot MTF |cos(π·f·d)| then has its first zero at Nyquist, f = 1/(2·pitch). */
143
149
  const AA_DEFAULT_SEPARATION_SITES = 1;
144
150
  /** Parses `sensor.antiAliasing` into engine point-splitting components (offsets in µm, +x right, +y down). */
@@ -920,8 +926,15 @@ export async function runCapture(options, run, viewOption, renderView) {
920
926
  // 2.6: a chart preset's evaluator is engine-built end to end (its results: the schema's exact keys, constant fields,
921
927
  // the request's identifiers, deep-frozen FACADE_EVIDENCE, UNCERTAINTY and limitations, and an engine-computed
922
928
  // radiance), so the native task may skip re-parsing the fixed parts. Caller scenes keep the full validation.
923
- if (chart)
929
+ // #394: a caller material scene's evaluator is engine-built in the same way (its radiance comes from the validated materials;
930
+ // only materialAt, validated on every call, is the caller's).
931
+ const materialEvaluator = chart !== undefined || callerScene?.kind === "material-function";
932
+ if (materialEvaluator)
924
933
  markInternalSceneRadianceEvaluator(provider.evaluateRadiance);
934
+ // #394: its results are materialRadiance(material at the queried point, wavelength), so the lean executor may tabulate them.
935
+ const materialOfDirection = planarProviderMaterialOfDirection(provider);
936
+ if (materialEvaluator && materialOfDirection && provider.materialRadiance)
937
+ registerInternalRadianceAccess(provider.evaluateRadiance, { keyOfDirection: materialOfDirection, radianceOfKey: provider.materialRadiance, tabulated: true });
925
938
  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.");
926
939
  // Metering (CIPA DC-004 standard output sensitivity and camera equation) and exposure-mode resolution
927
940
  const exposureOptions = allowKeys(top.exposure, "exposure", ["mode", "iso", "shutterSeconds", "compensationStops"]);
@@ -1303,13 +1316,16 @@ export async function runCapture(options, run, viewOption, renderView) {
1303
1316
  const yieldControl = createHostYield().yieldControl;
1304
1317
  const perSite = queriesPerSite * (diffraction && psf === diffractionPsf ? (2 * diffraction.kernelRadiusSamples + 1) ** 2 : 1);
1305
1318
  let diffractionMs = 0, demosaicMs, encodeMs;
1306
- const rawProvider = diffraction && diffractionObjectSpace ? createDiffractedPlanarProvider({ base: provider, psf: diffractionPsf.configuration.psf,
1319
+ const diffractedProvider = (window) => diffraction && diffractionObjectSpace ? createDiffractedPlanarProvider({ base: provider, psf: diffractionPsf.configuration.psf,
1307
1320
  wavelengthsNm: diffraction.profile.axes.wavelengthNm, kernelRadius: diffraction.kernelRadiusSamples, nativeWidth, nativeHeight, outputWidth: width,
1308
1321
  sitePitchMicrometers: pitch, distanceM: subjectDistanceM, metresPerFrameUnit, onConvolution: (ms) => { diffractionMs += ms; },
1309
1322
  // Region (2.6): convolve only the lattice the region's native sites query; the values are the whole render's.
1310
- ...(regionNative ? { window: regionNative } : {}),
1323
+ ...(window ? { window } : {}),
1311
1324
  // The environment queries each site at each aperture node minus each split offset (sites, +x right, +y down).
1312
1325
  ...(antiAliasing || boxSamples > 1 ? { queryOffsetsSites: apertureNodeOffsets(boxSamples).flatMap((node) => (antiAliasing ? antiAliasing.components : [{ offsetMicrometers: { x: 0, y: 0 } }]).map((c) => ({ x: node.x - c.offsetMicrometers.x / pitch, y: node.y - c.offsetMicrometers.y / pitch }))) } : {}) }) : provider;
1326
+ // Built on first use: a capture that takes the tabulated path (#394 step 2) never queries it, and its kernels cost a setup.
1327
+ let rawProviderBuilt;
1328
+ const rawProvider = () => (rawProviderBuilt ??= diffractedProvider(regionNative));
1313
1329
  if (antiAliasing && perSite > 100_000)
1314
1330
  fail("sensor.antiAliasing with these options needs " + perSite + " scene queries per site; the environment admits at most 100000.");
1315
1331
  const evaluations = nativeWidth * nativeHeight * perSite;
@@ -1325,7 +1341,7 @@ export async function runCapture(options, run, viewOption, renderView) {
1325
1341
  // is linear in the radiance node values and, for this planar scene, the same at every site (paraxial π/(4N_w²)).
1326
1342
  const cx = Math.floor(nativeWidth / 2) & ~1, cy = Math.floor(nativeHeight / 2) & ~1;
1327
1343
  const channelSites = { red: cy * nativeWidth + cx, green: cy * nativeWidth + Math.min(cx + 1, nativeWidth - 1), blue: Math.min(cy + 1, nativeHeight - 1) * nativeWidth + Math.min(cx + 1, nativeWidth - 1) };
1328
- const electronsPerRadiance = {};
1344
+ const environments = {};
1329
1345
  for (const channel of CHANNELS) {
1330
1346
  if (channelAt(channelSites[channel] % nativeWidth, Math.floor(channelSites[channel] / nativeWidth)) !== channel)
1331
1347
  fail(caller + " needs at least a 2 × 2 native raster.");
@@ -1336,13 +1352,36 @@ export async function runCapture(options, run, viewOption, renderView) {
1336
1352
  environment.temporalSampleCount = 1;
1337
1353
  delete environment.pupil;
1338
1354
  delete environment.lensFieldMapping;
1339
- const electrons = (radiance) => calculateEnvironmentSensorPhotoSignal({ ...environment,
1340
- evaluateRadiance: (request) => ({ ...provider.evaluateRadiance(request), spectralRadianceWattsPerSquareMeterSteradianNanometer: radiance(request.wavelengthNanometers) })
1341
- }).value
1342
- .photo.value.photoSignal.expectedGeneratedElectronCount;
1343
- const nodes = new Set();
1344
- electrons((nm) => { nodes.add(nm); return 1; });
1345
- electronsPerRadiance[channel] = new Map([...nodes].sort((a, b) => a - b).map((node) => [node, electrons((nm) => (nm === node ? 1 : 0))]));
1355
+ delete environment.evaluateRadiance;
1356
+ delete environment.evaluateApertureRadiance;
1357
+ environments[channel] = environment;
1358
+ }
1359
+ // The responses are a function of these records alone (the radiance is replaced per node), and a request split into region
1360
+ // tiles builds the same records for every tile, so they are kept across calls (the last CHANNEL_RESPONSE_CACHE_SIZE).
1361
+ const key = JSON.stringify(CHANNELS.map((c) => environments[c]));
1362
+ const copy = (responses) => Object.fromEntries(CHANNELS.map((c) => [c, new Map(responses[c])]));
1363
+ const cached = channelResponseCache.get(key);
1364
+ let electronsPerRadiance;
1365
+ if (cached) {
1366
+ channelResponseCache.delete(key);
1367
+ channelResponseCache.set(key, cached);
1368
+ electronsPerRadiance = copy(cached);
1369
+ }
1370
+ else {
1371
+ electronsPerRadiance = {};
1372
+ for (const channel of CHANNELS) {
1373
+ // Planned once per channel and executed per spectral node: calculateEnvironmentSensorPhotoSignal is this plan and execution.
1374
+ const photoPlan = planEnvironmentSensorPhotoSignal(environments[channel]);
1375
+ const electrons = (radiance) => executeEnvironmentSensorPhotoSignal(photoPlan, (request) => ({ ...provider.evaluateRadiance(request), spectralRadianceWattsPerSquareMeterSteradianNanometer: radiance(request.wavelengthNanometers) })).value
1376
+ .photo.value.photoSignal.expectedGeneratedElectronCount;
1377
+ const nodes = new Set();
1378
+ electrons((nm) => { nodes.add(nm); return 1; });
1379
+ electronsPerRadiance[channel] = new Map([...nodes].sort((a, b) => a - b).map((node) => [node, electrons((nm) => (nm === node ? 1 : 0))]));
1380
+ }
1381
+ // Callers get their own maps; the cached ones are never handed out.
1382
+ channelResponseCache.set(key, copy(electronsPerRadiance));
1383
+ if (channelResponseCache.size > CHANNEL_RESPONSE_CACHE_SIZE)
1384
+ channelResponseCache.delete(channelResponseCache.keys().next().value);
1346
1385
  }
1347
1386
  const wavelengths = [...new Set(CHANNELS.flatMap((c) => [...electronsPerRadiance[c].keys()]))].sort((a, b) => a - b);
1348
1387
  // 2. Electrons → normalized RAW signal: (code − black)/(white − black) = e·d/(G·(W − B)) for the linear readout
@@ -1471,12 +1510,45 @@ export async function runCapture(options, run, viewOption, renderView) {
1471
1510
  return () => { completed++; onProgress?.({ stage, completedTileCount: completed, totalTileCount: total }); };
1472
1511
  };
1473
1512
  const rawTick = progress("raw", regionNative ? regionNative.height * Math.ceil(regionNative.width / tileWidth) : plan.tileCount);
1474
- const rawCallbacks = { readTile: async (request) => { const tile = await readTile(request); rawTick(); return tile; },
1475
- evaluateRadiance: rawProvider.evaluateRadiance, ...(pupil ? { evaluateApertureRadiance: provider.evaluateApertureRadiance } : {}), yieldControl };
1476
- const rawTask = regionNative ? createNativeEnvironmentRawRegionTask(environmentRaw, regionNative, rawCallbacks) :
1477
- createNativeEnvironmentRawTask(environmentRaw, rawCallbacks);
1478
- await runTask(rawTask);
1479
- let rawOutput = rawTask.takeOutput();
1513
+ // #394: siteInput builds fresh site records per call and never changes them, so the native task plans them without a copy.
1514
+ const rawCallbacks = () => ({ readTile: markInternalNativeTileReader(async (request) => { const tile = await readTile(request); rawTick(); return tile; }),
1515
+ evaluateRadiance: rawProvider().evaluateRadiance, ...(pupil ? { evaluateApertureRadiance: provider.evaluateApertureRadiance } : {}), yieldControl });
1516
+ // #394 step 2: an engine-built planar scene (chart preset or caller material scene; still, rectilinear, no distortion, vignetting,
1517
+ // pupil rays or caller PSF) takes its expected electrons from the tabulated signal, verified per CFA phase against the chain.
1518
+ const tabulatedScene = chart !== undefined || callerScene?.kind === "material-function";
1519
+ let tabulated;
1520
+ if (tabulatedScene && !pupil && !lensFieldMapping && !vignetting && !lensPsf && !rotating && (!diffraction || diffractionObjectSpace) && provider.chartMaterialAt && provider.materialRadiance) {
1521
+ const { electronsPerRadiance, wavelengths } = axisChannelResponse("simulateCapture");
1522
+ const perNode = CHANNELS.map((c) => Float64Array.from(wavelengths, (nm) => electronsPerRadiance[c].get(nm) ?? 0));
1523
+ const sceneMaterials = chart ? chart.materials : callerScene.materials, materialRadiance = provider.materialRadiance;
1524
+ const { signalInput } = buildExpectedSignalInput({ nativeWidth, nativeHeight, width, wavelengths, perNode, apertureNodes: apertureNodeOffsets(boxSamples), boxSamples, antiAliasing, pitch,
1525
+ diffraction: diffraction ? { radius: diffraction.kernelRadiusSamples, psf: diffractionPsf.configuration.psf, profileWavelengths: diffraction.profile.axes.wavelengthNm } : undefined,
1526
+ materials: { materialAt: provider.chartMaterialAt, whiteRadiance: (nm) => irradianceScale * illuminant.shape.evaluate(nm) / Math.PI,
1527
+ list: [...sceneMaterials.map((m, index) => (m.kind === "neutral" ? { neutral: m.reflectance } : { radiance: (nm) => materialRadiance(index, nm) })),
1528
+ ...(chartSize ? [{ neutral: chartSize.surroundReflectance }] : [])] },
1529
+ radianceAt: provider.radianceAt, imagingArea, focalLengthMm, focusDistanceM, subjectDistanceM, metresPerFrameUnit, caller: "simulateCapture" });
1530
+ // The verification reads only the region's top-left 2 × 2 block, so its diffraction convolves only that block's lattice; the
1531
+ // window never changes a value (see diffraction-provider.ts), so the chain it runs is the whole render's.
1532
+ const verificationProvider = diffractedProvider({ x: regionNative?.x ?? 0, y: regionNative?.y ?? 0, width: 2, height: 2 });
1533
+ tabulated = await renderTabulatedNativeRaw({ input: environmentRaw, region: regionNative, siteInput, evaluateRadiance: verificationProvider.evaluateRadiance,
1534
+ electrons: createDisplayViewExpectedSignal(signalInput), onTile: rawTick, tileWidth, yieldControl: () => yieldControl(), checkAbort });
1535
+ }
1536
+ let rawOutput;
1537
+ if (tabulated) {
1538
+ rawOutput = { raw: tabulated.raw, providerEvaluationCount: tabulated.providerEvaluationCount };
1539
+ // The verification's relative difference itself is not reported: it is a last-bits quantity, and captures use the JavaScript
1540
+ // engine's own Math functions, so it differs between engines while the results are the same.
1541
+ assume("raw.expectedSignal", "model", { method: "tabulated-expected-electrons", verificationSites: 4, tolerance: TABULATED_RELATIVE_TOLERANCE }, "Each native site's expected photo electrons are the tabulated sum over the pixel-aperture nodes, anti-aliasing spots, diffraction taps and spectral nodes (the display view's expected signal), " +
1542
+ "the same quantity the environment chain computes for this still, rectilinear, distortion-free planar scene, formed in another order, so it can differ in the last bits. The sensor stage (dark charge, Poisson draws, " +
1543
+ "read noise, ADC) is the native one with each site's own seeds. One site of each CFA phase ran the full chain: its readout was reproduced bit for bit and its electrons agreed within the tolerance. " +
1544
+ "diagnostics.providerEvaluationCount counts those sites' evaluator calls and the table's scene lookups.");
1545
+ }
1546
+ else {
1547
+ const rawTask = regionNative ? createNativeEnvironmentRawRegionTask(environmentRaw, regionNative, rawCallbacks()) :
1548
+ createNativeEnvironmentRawTask(environmentRaw, rawCallbacks());
1549
+ await runTask(rawTask);
1550
+ rawOutput = rawTask.takeOutput();
1551
+ }
1480
1552
  // Region (2.3): the region's sites, re-described by a plan of the region raster (active area = the region), for development.
1481
1553
  let developmentPlan = plan;
1482
1554
  if (region && regionNative) {
@@ -1510,14 +1582,23 @@ export async function runCapture(options, run, viewOption, renderView) {
1510
1582
  sampleSet: { imageStateId: "facade-pre-wb-raw", inputDomain: "relative-pre-wb-camera-linear-rgb", samples } });
1511
1583
  finalRawInput = { raw: buildRawInput(intentOf(whiteBalanceState)), sceneBinding, maximumProviderEvaluations: environmentRaw.maximumProviderEvaluations };
1512
1584
  const wbTick = progress("white-balance", plan.tileCount);
1513
- // A second lean RAW pass with the measured intent. It gives the same RAW as re-running the sensor stage on a
1514
- // retained optical stage, and is faster: retaining the optical stage takes the first pass off the lean executor.
1515
- const wbTask = createNativeEnvironmentRawTask(finalRawInput, {
1516
- readTile: async (request) => { const tile = await readTile(request); wbTick(); return tile; },
1517
- evaluateRadiance: rawProvider.evaluateRadiance, ...(pupil ? { evaluateApertureRadiance: provider.evaluateApertureRadiance } : {}), yieldControl
1518
- });
1519
- await runTask(wbTask);
1520
- rawOutput = wbTask.takeOutput();
1585
+ if (tabulated) {
1586
+ // #394: the RAW codes do not depend on the white-balance intent (it is development metadata), so the tabulated RAW is
1587
+ // kept with the plan of the measured intent instead of being rendered again.
1588
+ rawOutput = { ...rawOutput, raw: { ...rawOutput.raw, plan: calculateNativeRawPlan(finalRawInput.raw) } };
1589
+ for (let k = 0; k < plan.tileCount; k++)
1590
+ wbTick();
1591
+ }
1592
+ else {
1593
+ // A second lean RAW pass with the measured intent. It gives the same RAW as re-running the sensor stage on a
1594
+ // retained optical stage, and is faster: retaining the optical stage takes the first pass off the lean executor.
1595
+ const wbTask = createNativeEnvironmentRawTask(finalRawInput, {
1596
+ readTile: markInternalNativeTileReader(async (request) => { const tile = await readTile(request); wbTick(); return tile; }),
1597
+ evaluateRadiance: rawProvider().evaluateRadiance, ...(pupil ? { evaluateApertureRadiance: provider.evaluateApertureRadiance } : {}), yieldControl
1598
+ });
1599
+ await runTask(wbTask);
1600
+ rawOutput = wbTask.takeOutput();
1601
+ }
1521
1602
  }
1522
1603
  const wbMs = Date.now() - wbStarted, devStarted = Date.now();
1523
1604
  const colorProfile = { schemaVersion: "0.1.0", profileId: IDS.spectral, profileVersion: SIMULATE_CAPTURE_FACADE_VERSION, colorSamplingProfileId: IDS.cfa,