@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.
- package/CHANGELOG.md +39 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/capture/export-jpeg.js +7 -1
- package/dist/capture/export-jpeg.js.map +1 -1
- package/dist/capture/native-environment-lean.js +5 -2
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-raw.js +2 -2
- package/dist/capture/native-environment-raw.js.map +1 -1
- package/dist/capture/native-raw-development.js +1 -1
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/capture/native-raw.d.ts +16 -0
- package/dist/capture/native-raw.js +16 -3
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/packed-sensor-raw.d.ts +7 -0
- package/dist/capture/packed-sensor-raw.js +6 -2
- package/dist/capture/packed-sensor-raw.js.map +1 -1
- package/dist/core/pixel-limits.d.ts +14 -0
- package/dist/core/pixel-limits.js +40 -0
- package/dist/core/pixel-limits.js.map +1 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/chart-radiance-batch.d.ts +7 -0
- package/dist/facade/chart-radiance-batch.js +7 -3
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +30 -0
- package/dist/facade/diffraction-provider.js +127 -0
- package/dist/facade/diffraction-provider.js.map +1 -0
- package/dist/facade/host-yield.d.ts +1 -1
- package/dist/facade/host-yield.js +0 -4
- package/dist/facade/host-yield.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +88 -2
- package/dist/facade/simulate-capture.js +343 -41
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/motion/temporal-exposure-sum.d.ts +7 -0
- package/dist/motion/temporal-exposure-sum.js +6 -2
- package/dist/motion/temporal-exposure-sum.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.d.ts +118 -0
- package/dist/optics/diffraction-psf-profile.js +252 -0
- package/dist/optics/diffraction-psf-profile.js.map +1 -0
- package/dist/optics/irradiance-convolution.d.ts +7 -0
- package/dist/optics/irradiance-convolution.js +6 -2
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/output/native-capture-sdr.d.ts +7 -0
- package/dist/output/native-capture-sdr.js +6 -3
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/png-encoder.d.ts +10 -1
- package/dist/output/png-encoder.js +10 -4
- package/dist/output/png-encoder.js.map +1 -1
- package/dist/output/print-jpeg.d.ts +18 -0
- package/dist/output/print-jpeg.js +15 -4
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/sensor/sampling-aperture.d.ts +15 -0
- package/dist/sensor/sampling-aperture.js +31 -10
- package/dist/sensor/sampling-aperture.js.map +1 -1
- package/docs/API_REFERENCE.md +27 -1
- package/docs/QUICK_START.md +285 -13
- package/docs/USAGE.md +3 -1
- package/docs/api/capture-native-raw.md +11 -2
- package/docs/api/capture-packed-sensor-raw.md +7 -1
- package/docs/api/core-pixel-limits.md +19 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +62 -2
- package/docs/api/facade-chart-radiance-batch.md +8 -2
- package/docs/api/facade-simulate-capture.md +83 -3
- package/docs/api/motion-temporal-exposure-sum.md +7 -1
- package/docs/api/optics-diffraction-psf-profile.md +96 -0
- package/docs/api/optics-irradiance-convolution.md +7 -1
- package/docs/api/output-native-capture-sdr.md +7 -2
- package/docs/api/output-png-encoder.md +8 -1
- package/docs/api/output-print-jpeg.md +17 -0
- package/package.json +3 -2
package/docs/QUICK_START.md
CHANGED
|
@@ -50,7 +50,7 @@ the example above is [simulate-capture.mjs](examples/simulate-capture.mjs).
|
|
|
50
50
|
| `request` | The complete underlying request: `environmentRaw` (the native environment RAW input), `siteInput(index)` (rebuilds the physical input of any sensor site), `development` (the RAW development input) and `whiteBalanceState` |
|
|
51
51
|
| `exposure` | Mode, resolved settings, EV100, metered scene luminance and the exposure-mode resolver's full output |
|
|
52
52
|
| `scene` | Chart parameters, illuminant, distance, framed size and the scene's SHA-256 digest (it covers caller illuminant and reflectance spectra; see `sourceSha256`) |
|
|
53
|
-
| `diagnostics` | Radiance query count, highlight and rendering counters,
|
|
53
|
+
| `diagnostics` | Radiance query count, highlight and rendering counters, per-stage timings (`timingsMs`) and, from 2.1, the opt-ins that ran (`options`); see [Cost of each option](#cost-of-each-option) |
|
|
54
54
|
| `provenance` | Status (`approximation`), camera preset identity, evidence, the composed contracts in order, and limitations |
|
|
55
55
|
| `assumptions` | Every default, derived value and fixed modelling choice, each with an explanation |
|
|
56
56
|
|
|
@@ -71,6 +71,7 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
|
|
|
71
71
|
| `lens.distortion` | none | `{ kind: "radial", profile }` or `{ kind: "brown-conrady", profile }`. |
|
|
72
72
|
| `lens.vignetting` | none | An `IlluminationVignettingProfile` (radial relative illumination). |
|
|
73
73
|
| `lens.psf` | none | A `{ kind: "sampled-local", configuration }` sampled lens PSF, passed to the environment contract unchanged. |
|
|
74
|
+
| `lens.diffraction` | off | 2.1: `"circular-aperture"` or `{ aperture: "circular-aperture", kernelRadiusSamples }`: diffraction blur of an ideal lens at the resolved f-number (see [Optics](#optics)). |
|
|
74
75
|
| `exposure.mode` | `"manual"` | Also `"aperture-priority"`, `"shutter-priority"`, `"program"`. |
|
|
75
76
|
| `exposure.iso` | 100 | Selects the body's ISO readout regime (gain and read noise). |
|
|
76
77
|
| `exposure.shutterSeconds` | 1/125 | Used in `manual` and `shutter-priority`. |
|
|
@@ -79,31 +80,34 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
|
|
|
79
80
|
| `subjectDistanceM` | 5 | Distance of the scene plane. |
|
|
80
81
|
| `illuminant` | Planckian 6500 K | Also `"cie-a"`, `{ kind: "planckian", temperatureKelvin }`, `{ kind: "daylight", correlatedColorTemperatureKelvin, basis }` or `{ kind: "spectrum", spectrum }`. |
|
|
81
82
|
| `illuminanceLux` | 21 481 (π·80 000/11.7) | See [Exposure](#exposure). |
|
|
82
|
-
| `output.width`, `output.height` | 64 × 64 | Output pixels. The sensor has one extra site on each side for demosaicing (see `output.borderHandling`). |
|
|
83
|
+
| `output.width`, `output.height` | 64 × 64 | Output pixels, 1–4094 each. The sensor has one extra site on each side for demosaicing (see `output.borderHandling`). |
|
|
84
|
+
| `output.maximumPixels` | off | Opt-in large images (2.2): the most native sensor sites the capture may allocate, up to `NATIVE_RAW_LIMITS.ceilingPixels` (2^27). Sides may then reach 16 384 sites including the margin (see [Large images](#large-images)). |
|
|
83
85
|
| `output.format` | `"srgb-8"` | Also `"srgb-16"`, `"jpeg"`, `"png"` and `"dng"` (see [Output formats](#output-formats)). |
|
|
84
86
|
| `output.colorEncoding` | `"srgb"` | PNG only: also `"display-p3"` and `"rec2020"`. |
|
|
85
87
|
| `output.bitDepth` | 8 | PNG only: 8 or 16. |
|
|
86
88
|
| `output.gamutMapping` | `"clip"` | PNG only: or `"perceptual"`. |
|
|
87
89
|
| `output.dng.capturedAtUtc` | none | DNG only: capture time for the date tags. |
|
|
88
|
-
| `output.demosaic` | the 1.0 bilinear kernels | `"bilinear"` (see [Demosaic](#demosaic)). |
|
|
90
|
+
| `output.demosaic` | the 1.0 bilinear kernels | `"bilinear"` or, from 2.1, `"ahd"` (see [Demosaic](#demosaic)). |
|
|
89
91
|
| `output.borderHandling` | `"margin"` | Or `"mirror"`. |
|
|
90
92
|
| `output.toneCurve` | `"identity"` | Any `SdrToneCurve`, for example `"positive-reinhard-per-channel"`. |
|
|
91
93
|
| `output.toneCurveParameters` | none | Required by the luminance curves (`extended-reinhard-luminance`, `naka-rushton-luminance`); the facade invents no curve parameters. |
|
|
92
94
|
| `output.highlightMode` | `"clip"` | Or `"reconstruct"`. |
|
|
93
95
|
| `output.whiteBalance` | `"scene-illuminant"` | Also `"auto"`, `"none"`, `{ kelvin, tint? }` and `{ channelGains }`. |
|
|
94
96
|
| `output.jpegQuantizationStep` | 2 | JPEG only: constant step 1–255, not a manufacturer quality scale. |
|
|
95
|
-
| `sensor.pixelPitchMicrometers` | 6 | The sensor is `width × pitch` wide (see [Geometry](#geometry)). |
|
|
97
|
+
| `sensor.pixelPitchMicrometers` | 6 | 0.5–100. The sensor is `width × pitch` wide (see [Geometry](#geometry)); the ISO readout scales with the pixel area (see [ISO and pixel size](#iso-and-pixel-size)). |
|
|
96
98
|
| `sensor.spectralStepNanometers` | 20 | Spectral quadrature over 380–780 nm. |
|
|
97
99
|
| `sensor.temporalSampleCount` | 1, or 8 when the camera rotates | Midpoints across each site's exposure window. |
|
|
98
100
|
| `sensor.noiseSeed` | 1 | Output is deterministic for a given seed. |
|
|
101
|
+
| `sensor.antiAliasing` | off | 2.1: an optical low-pass (anti-aliasing) filter: `{ kind: "four-spot", separationSites? }`, `{ kind: "two-spot", axis, separationSites? }` or `{ kind: "point-splitting", components }` (see [Anti-aliasing filter](#anti-aliasing-filter)). |
|
|
99
102
|
| `motion.angularVelocityRadPerSec` | still | `{ pitch?, yaw?, roll? }` in rad/s (see [Motion](#motion)). |
|
|
100
103
|
| `motion.shutter` | `"global"` | Or `"rolling"`. |
|
|
101
104
|
| `motion.rollingShutterSkewSeconds` | the body's readout skew | Rolling only. |
|
|
102
|
-
| `signal`, `onProgress` | none | An `AbortSignal` cancels the running task; `onProgress` reports `raw`, `white-balance`, `development` and `jpeg` tiles. The last event of each stage has `completedTileCount === totalTileCount`. |
|
|
105
|
+
| `signal`, `onProgress` | none | An `AbortSignal` cancels the running task; `onProgress` reports `raw`, `white-balance`, `development` and `jpeg` tiles. The last event of each stage has `completedTileCount === totalTileCount`. The capture yields to the host about every 8 ms with a `MessageChannel` message (else `setImmediate`, else `setTimeout`), so other queued tasks run in between: an abort sent from another task, such as a Web Worker `message` event, takes effect at the next yield. |
|
|
103
106
|
|
|
104
107
|
Unknown fields are rejected, so a typo never silently falls back to a default.
|
|
105
108
|
|
|
106
|
-
Every option added in 2.0 (PNG, demosaic, optics and motion)
|
|
109
|
+
Every option added in 2.0 (PNG, demosaic, optics and motion) and in 2.1 (`lens.diffraction`,
|
|
110
|
+
`output.demosaic: "ahd"`, `sensor.antiAliasing`) is opt-in. With none of them given, the
|
|
107
111
|
request, RAW, samples and JPEG are byte-identical to the 1.0 facade at the same illuminance, and a
|
|
108
112
|
test checks that. (The default illuminance changed before release; see [Exposure](#exposure).)
|
|
109
113
|
|
|
@@ -118,6 +122,39 @@ pixels is therefore a 0.38 mm crop of a large sensor, not a downscaled full fram
|
|
|
118
122
|
signal per pixel, and so the noise and the ISO behaviour, physically consistent with the tier's
|
|
119
123
|
sensor values.
|
|
120
124
|
|
|
125
|
+
### ISO and pixel size
|
|
126
|
+
|
|
127
|
+
The tier's ISO signal chain (conversion gain, saturation signal and read noise per ISO setting) is
|
|
128
|
+
declared for the default 6 µm pixel. At other pitches the facade keeps the ISO calibration
|
|
129
|
+
independent of pixel size, so the same exposure gives the same image brightness at any pitch:
|
|
130
|
+
|
|
131
|
+
- Standard output sensitivity S = 10/H (CIPA DC-004-2020 §3.2) is defined by the camera's output
|
|
132
|
+
for a focal-plane exposure H; it does not depend on pixel size.
|
|
133
|
+
- The charge a pixel collects is proportional to its area: the photon count is μp = A·E·t/(hν)
|
|
134
|
+
(EMVA Standard 1288 Release 4.0 Linear, §2.1, Eq. 3, free).
|
|
135
|
+
- So the facade multiplies the system conversion gain (e⁻ per code) and the pre-ADC saturation
|
|
136
|
+
signal of every ISO regime by the area ratio pitch²/36 µm². Codes, black level, ADC and digital gain
|
|
137
|
+
are unchanged. At 6 µm the ratio is exactly 1 and nothing is changed (byte-identical to 2.1.0).
|
|
138
|
+
- Read noise (in e⁻) and the synthetic dark current (1 e⁻/s) stay per pixel.
|
|
139
|
+
|
|
140
|
+
What you see: brightness stays the same, and per-pixel noise rises as pixels shrink, because a
|
|
141
|
+
smaller pixel collects fewer electrons for the same exposure (shot-noise SNR = √N_e). At f/8, 1/125 s,
|
|
142
|
+
ISO 100 on an 18 % grey card, the 8-bit sRGB mean is 101.2 at 30, 6 and 3 µm, and the green standard
|
|
143
|
+
deviation is 0.18, 0.93 and 1.83 codes. Below about 1 µm the 8-bit sRGB mean falls slightly (about 1 code at 0.55 µm)
|
|
144
|
+
although the linear RAW mean is unchanged (within 0.3 %): strong noise passed through the concave
|
|
145
|
+
sRGB curve lowers the mean of the encoded values.
|
|
146
|
+
|
|
147
|
+
The charge capacity follows the scaled base-ISO saturation signal (see `sensor.capacityElectrons`),
|
|
148
|
+
so it is proportional to pixel area: 60 000 e⁻ at 6 µm, 15 000 e⁻ at 3 µm, 1 500 000 e⁻ at 30 µm.
|
|
149
|
+
This is a consequence of holding the tier's ISO rating and its half stop of highlight headroom fixed,
|
|
150
|
+
**not a measured full-well model**. Published sensor data show full-well capacity growing with pixel
|
|
151
|
+
size but less than in proportion to area across process generations (for example about 16 000 e⁻ at
|
|
152
|
+
2 µm and 37 500 e⁻ at 5.2 µm in Farrell, Xiao & Kavusi, *Proc. SPIE* 6069, 2006, Table 1; Chen et al.,
|
|
153
|
+
*Proc. SPIE* 3965, 2000, model the photodiode capacitance as roughly linear in pixel side). A real
|
|
154
|
+
large-pixel sensor may therefore saturate earlier, and a real small-pixel sensor would usually be
|
|
155
|
+
given a higher base ISO. When the pitch is not 6 µm, the assumption `sensor.isoAreaNormalization`
|
|
156
|
+
records the area ratio and this approximation.
|
|
157
|
+
|
|
121
158
|
## Exposure
|
|
122
159
|
|
|
123
160
|
The facade meters the scene like a reflected-light meter:
|
|
@@ -217,14 +254,15 @@ These choices appear in `assumptions` as `kind: "model"` entries:
|
|
|
217
254
|
lens capability. By default no PSF (point-spread function) or diffraction, vignetting, distortion,
|
|
218
255
|
lateral colour or stray light is applied. The tier's optical data exist only at f/4 and 5 m, and
|
|
219
256
|
the facade does not extrapolate them. Vignetting, distortion and PSF are applied only from profiles
|
|
220
|
-
you supply.
|
|
257
|
+
you supply; diffraction of an ideal lens only with `lens.diffraction` (2.1).
|
|
221
258
|
- **Sensor.** Square pixels with a fill factor of 1 and no anti-aliasing filter. A global
|
|
222
259
|
electronic shutter unless `motion.shutter` is `"rolling"`. A synthetic uniform dark current of
|
|
223
|
-
1 e⁻/s at 20 °C (the tiers declare none). The charge capacity equals the body's base-ISO saturation signal
|
|
260
|
+
1 e⁻/s at 20 °C (the tiers declare none). The charge capacity equals the body's base-ISO saturation signal,
|
|
261
|
+
scaled with pixel area away from 6 µm (see [ISO and pixel size](#iso-and-pixel-size)).
|
|
224
262
|
- **Demosaic.** Bilinear RGGB interpolation through the existing linear-kernel contract, unless
|
|
225
|
-
`output.demosaic` selects a built-in kernel. (Demosaicing fills in the two missing colours at each
|
|
263
|
+
`output.demosaic` selects a built-in kernel or AHD. (Demosaicing fills in the two missing colours at each
|
|
226
264
|
pixel of a colour-filter mosaic.) Sharp edges without lens blur therefore show the usual zipper and
|
|
227
|
-
false-colour artefacts.
|
|
265
|
+
false-colour artefacts; `"ahd"` and `lens.diffraction` reduce them.
|
|
228
266
|
- **Scene.** A planar Lambertian surface: radiance = reflectance × irradiance / π, with illuminance
|
|
229
267
|
from K_m = 683 lm/W and the analytic ȳ. No shadows, inter-reflection, gloss or fluorescence.
|
|
230
268
|
- **Rendering.** Linear sRGB with RAW white at display white, no exposure push, out-of-gamut
|
|
@@ -323,9 +361,23 @@ console.log(dng.width, dng.activeArea, dng.whiteLevel, dng.codes.length === shot
|
|
|
323
361
|
|
|
324
362
|
## Demosaic
|
|
325
363
|
|
|
326
|
-
`output.demosaic` selects
|
|
327
|
-
|
|
328
|
-
|
|
364
|
+
`output.demosaic` selects:
|
|
365
|
+
|
|
366
|
+
- `"bilinear"`: the built-in Bayer kernels of `createBuiltInBayerDemosaicProfiles` (nearest same-channel
|
|
367
|
+
averages; pixel-identical to the default).
|
|
368
|
+
- `"ahd"` (2.1): adaptive homogeneity-directed demosaicing, `createAhdDemosaicTask` (after Hirakawa &
|
|
369
|
+
Parks, *IEEE Trans. Image Processing* 14(3), 2005). Green is interpolated along rows and along columns,
|
|
370
|
+
red and blue follow from colour differences, and each pixel takes the more homogeneous of the two
|
|
371
|
+
candidates in CIELAB. It is non-linear and edge-directed, so it shows much less zipper and false colour
|
|
372
|
+
on sharp edges than bilinear (mean false colour of a neutral 32 × 32 Siemens star: 138 instead of 209
|
|
373
|
+
8-bit codes; slanted edge: 5.3 instead of 11.4). The RAW sites are normalized and white-level clipped
|
|
374
|
+
exactly as in the native development, the homogeneity is measured on white-balanced colour, and the
|
|
375
|
+
native development's colour operator and SDR kernel follow. The paper's iterative median step is not
|
|
376
|
+
applied. With `"margin"` borders the sensor gets 5 extra sites per side (an AHD pixel depends on sites
|
|
377
|
+
up to 5 away); `request.development` keeps the bilinear kernels, which validate the development but do
|
|
378
|
+
not produce the pixels.
|
|
379
|
+
|
|
380
|
+
The Malvar–He–Cutler kernels are not offered (a patent is still in force; see
|
|
329
381
|
[PROCESSING.md](https://github.com/Photivra/photivra/blob/main/docs/PROCESSING.md)).
|
|
330
382
|
|
|
331
383
|
`output.borderHandling` decides how edge pixels get their neighbours:
|
|
@@ -365,6 +417,25 @@ The optics options feed only what the environment site contract accepts:
|
|
|
365
417
|
1 + r2·ρ² + r4·ρ⁴ + r6·ρ⁶, applied once at the physical sensor point before any PSF.
|
|
366
418
|
- `lens.psf` is a caller sampled PSF passed through unchanged. Its focal length, f-number and focus
|
|
367
419
|
must equal the resolved capture settings exactly, and it cannot be combined with pupil sampling.
|
|
420
|
+
- `lens.diffraction` (2.1) adds the diffraction blur of an ideal lens at the capture's resolved
|
|
421
|
+
f-number, so stopping down now softens the image as well as darkening it.
|
|
422
|
+
- The engine builds the kernels (`createDiffractionLimitedPsfProfile`): the Airy pattern of a uniformly
|
|
423
|
+
lit circular pupil (scalar Fraunhofer diffraction), aberration-free and in focus, with pupil diameter
|
|
424
|
+
f/N and the thin-lens image distance (working f-number N·v/f). Each kernel holds the PSF energy of
|
|
425
|
+
site-sized cells, one kernel per spectral node.
|
|
426
|
+
- The kernel window reaches the third dark ring at the longest node by default (2, 4 and 7 sites at
|
|
427
|
+
f/4, f/8 and f/16 with 6 µm pixels); set `kernelRadiusSamples` (0–32) to change it. The Airy tail
|
|
428
|
+
outside the window (2–5 % of the energy with the default) is listed per wavelength in the
|
|
429
|
+
`lens.diffraction` assumption and put back by renormalizing each kernel to unit sum.
|
|
430
|
+
- It runs as an object-space convolution: the scene radiance on the site lattice is convolved once per
|
|
431
|
+
spectral node and the provider answers each site query with it. That is the same sum as the
|
|
432
|
+
environment PSF quadrature (one scene query per kernel tap) for this still, distortion-free planar
|
|
433
|
+
scene; a test checks it to 1e-12 per site. With camera rotation, a projection or distortion, or
|
|
434
|
+
vignetting, the per-tap quadrature runs instead (much slower). `request.diffraction` holds the profile,
|
|
435
|
+
the equivalent `sampled-local` PSF and which execution ran.
|
|
436
|
+
- Not modelled: defocus, aberrations, pupil clipping, a polygonal (bladed) aperture (the tiers declare
|
|
437
|
+
no blade count) and the pixel aperture (each site is still sampled at its centre). It cannot be
|
|
438
|
+
combined with `lens.psf` or `lens.pupilSampling`.
|
|
368
439
|
|
|
369
440
|
The default frame is a 0.38 mm crop behind a 50 mm lens, a field of well under a degree. Projection
|
|
370
441
|
and distortion effects therefore only become visible with larger pixels (or a custom lens). The
|
|
@@ -397,6 +468,65 @@ const lensy = await simulateCapture({
|
|
|
397
468
|
console.log(lensy.request.siteInput(0).environment.lensFieldMapping, lensy.assumptions.find((a) => a.id === "lens.vignetting")?.kind);
|
|
398
469
|
```
|
|
399
470
|
|
|
471
|
+
```ts
|
|
472
|
+
import { simulateCapture } from "@photivra/engine";
|
|
473
|
+
|
|
474
|
+
const stoppedDown = await simulateCapture({
|
|
475
|
+
scene: { preset: "siemens-star" },
|
|
476
|
+
lens: { fNumber: 16, diffraction: "circular-aperture" },
|
|
477
|
+
exposure: { shutterSeconds: 1 / 30 },
|
|
478
|
+
output: { width: 48, height: 48, demosaic: "ahd" }
|
|
479
|
+
});
|
|
480
|
+
console.log(stoppedDown.request.diffraction?.execution, stoppedDown.request.diffraction?.profile.maximumTruncatedEnergyFraction);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Anti-aliasing filter
|
|
484
|
+
|
|
485
|
+
`sensor.antiAliasing` (2.1) puts an optical low-pass filter in front of the sensor. Most real cameras
|
|
486
|
+
with a Bayer sensor carry one; the generic tiers declare none, so it is off by default. It is modelled
|
|
487
|
+
the way a birefringent quartz filter works: every image point is split into equally bright copies, and
|
|
488
|
+
each site collects the light that would otherwise have landed at its centre minus each offset (the
|
|
489
|
+
engine's `normalized-point-splitting-kernel` optical-stack response, resolved by
|
|
490
|
+
`resolveAntiAliasingSpatialKernel`).
|
|
491
|
+
|
|
492
|
+
- `{ kind: "four-spot", separationSites }`: two crossed plates, four spots at (±d/2, ±d/2) with weight
|
|
493
|
+
1/4 each. Its MTF is |cos(π·f_x·d)·cos(π·f_y·d)|. The most common filter in digital cameras.
|
|
494
|
+
- `{ kind: "two-spot", axis: "horizontal" | "vertical", separationSites }`: one plate, two spots at
|
|
495
|
+
±d/2 along the axis, MTF |cos(π·f·d)|.
|
|
496
|
+
- `{ kind: "point-splitting", components }`: your own kernel, offsets in micrometres (+x right, +y
|
|
497
|
+
down) and positive weights that sum to 1; at most 16 components, each within 4 sites.
|
|
498
|
+
- d = `separationSites` × pixel pitch, 0 < `separationSites` ≤ 4. The default 1 puts the first MTF
|
|
499
|
+
zero at the sensor's Nyquist frequency 1/(2·pitch): a four-spot filter one pixel wide cuts off at
|
|
500
|
+
Nyquist (Palum 2006). Above Nyquist the cosine rises again, so a lens (for example
|
|
501
|
+
`lens.diffraction`) is still needed to suppress the highest frequencies.
|
|
502
|
+
|
|
503
|
+
Measured at f/8 on 128 × 128 neutral charts (mean false colour |R − G| + |B − G| in 8-bit codes):
|
|
504
|
+
Siemens star 100.8 → 52.2 with bilinear and 68.7 → 25.1 with AHD; slanted edge 4.9 → 3.8 (bilinear).
|
|
505
|
+
The price is sharpness: the slanted-edge 10–90 % width grows from 1.49 to 2.34 px (bilinear).
|
|
506
|
+
|
|
507
|
+
Each site queries the scene once per spot, so the radiance work grows by the number of spots (4 for
|
|
508
|
+
`four-spot`). With `lens.diffraction` the object-space convolution still applies: the taps around
|
|
509
|
+
each split point lie on the site lattice shifted by its offset, so the engine convolves once per
|
|
510
|
+
distinct sub-site shift (one half-site lattice for the default four-spot filter). A test checks it
|
|
511
|
+
against the per-tap environment quadrature to 1e-12 per site.
|
|
512
|
+
|
|
513
|
+
Approximations, listed in the `sensor.antiAliasing` assumption: the split is the same at every
|
|
514
|
+
wavelength, field point and polarization (birefringence dispersion is ignored), the spots are exact
|
|
515
|
+
points of equal weight for unpolarized light, and the filter's transmission, reflections and focus
|
|
516
|
+
shift are not modelled. It is not a measured filter.
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
import { simulateCapture } from "@photivra/engine";
|
|
520
|
+
|
|
521
|
+
const filtered = await simulateCapture({
|
|
522
|
+
scene: { preset: "siemens-star" },
|
|
523
|
+
sensor: { antiAliasing: { kind: "four-spot" } },
|
|
524
|
+
lens: { diffraction: "circular-aperture" },
|
|
525
|
+
output: { width: 48, height: 48, demosaic: "ahd" }
|
|
526
|
+
});
|
|
527
|
+
console.log(filtered.assumptions.find((a) => a.id === "sensor.antiAliasing")?.value);
|
|
528
|
+
```
|
|
529
|
+
|
|
400
530
|
## Motion
|
|
401
531
|
|
|
402
532
|
`motion.angularVelocityRadPerSec` rotates the camera at a constant rate during the exposure, in the
|
|
@@ -450,10 +580,147 @@ The 1000 × 1000 row is not a measurement. It multiplies the 256 × 256 CPU cost
|
|
|
450
580
|
count. Before the lean executor, the same 64 × 64 capture took about 6.0 s of CPU on this host, and
|
|
451
581
|
128 × 128 about 20.6 s. Development and encoding are a small part of the total.
|
|
452
582
|
|
|
583
|
+
The 2.1 options, measured on the same kind of host (4-vCPU Intel Xeon 2.80 GHz, Node 24, median of three
|
|
584
|
+
runs, slanted edge and Siemens star): at 48 × 48, default 0.38–0.45 s, `lens.diffraction` 0.49–0.71 s
|
|
585
|
+
(f/4–f/16), `"ahd"` 0.48–0.63 s, both 0.60–0.87 s; at 128 × 128, default 2.1–2.3 s, diffraction
|
|
586
|
+
2.5–3.4 s, AHD 2.4–2.5 s, both 2.8–4.0 s. The same diffraction through the per-tap environment quadrature
|
|
587
|
+
took 13.1 s at 48 × 48, f/8. [Cost of each option](#cost-of-each-option) covers every opt-in.
|
|
588
|
+
|
|
453
589
|
These numbers come from one run on one machine. They are not a performance claim. Small captures
|
|
454
590
|
(up to about 128 × 128) take a few seconds. Use them while you explore, and render large images only
|
|
455
591
|
when you need them.
|
|
456
592
|
|
|
593
|
+
## Cost of each option
|
|
594
|
+
|
|
595
|
+
Slow options are opt-in, so an application can decide which ones it can afford: a feature that is
|
|
596
|
+
too slow for a phone today may be cheap on the next generation of hardware. The table gives the
|
|
597
|
+
measured wall time of each opt-in that affects run time, relative to the default capture of the same
|
|
598
|
+
size.
|
|
599
|
+
|
|
600
|
+
**Method.** Slanted-edge chart, every other option at its default (f/8, 1/125 s, ISO 100, sRGB 8-bit,
|
|
601
|
+
bilinear demosaic). One fresh Node process per row and size; one 16 × 16 warm-up capture, then three
|
|
602
|
+
timed captures (`performance.now()` around `simulateCapture`), median reported. Built `dist`, Node
|
|
603
|
+
24.21.0, 4-vCPU Intel Xeon 2.80 GHz, Linux, nothing else running. One run on one machine: not a
|
|
604
|
+
performance claim, but the ratios carry over roughly.
|
|
605
|
+
|
|
606
|
+
| Option | 48 × 48 | 128 × 128 | What it buys |
|
|
607
|
+
| --- | --- | --- | --- |
|
|
608
|
+
| default | 0.50 s (1×) | 2.49 s (1×) | Point optics, bilinear demosaic, sRGB samples |
|
|
609
|
+
| `lens.diffraction: "circular-aperture"` | 0.65 s (1.3×) | 2.84 s (1.1×) | Aperture-dependent sharpness (Airy blur); convolution 0.38 s at 128 |
|
|
610
|
+
| `output.demosaic: "ahd"` | 0.65 s (1.3×) | 2.60 s (1.0×) | About half the false colour on sharp edges; 5-site margin, demosaic 0.05 s at 128 |
|
|
611
|
+
| `sensor.antiAliasing: { kind: "four-spot" }` | 0.94 s (1.9×) | 4.96 s (2.0×) | Optical low-pass filter: false colour halved again, at a cost in sharpness |
|
|
612
|
+
| anti-aliasing + diffraction | 1.16 s (2.3×) | 6.02 s (2.4×) | Both, still on the fast convolution path |
|
|
613
|
+
| anti-aliasing + diffraction + AHD | 1.42 s (2.8×) | 6.99 s (2.8×) | The most faithful still image of a planar chart |
|
|
614
|
+
| `lens.pupilSampling` 1 × 4 rays | 1.79 s (3.6×) | 11.5 s (4.6×) | Defocus and depth of field, coarse |
|
|
615
|
+
| `lens.pupilSampling` 2 × 8 rays | 5.20 s (10×) | 33.1 s (13×) | Defocus and depth of field, smoother bokeh |
|
|
616
|
+
| `lens.psf` (caller 3 × 3 kernel) | 2.11 s (4.2×) | 12.4 s (5.0×) | Any sampled lens PSF; runs per tap, so cost grows with the kernel size (a 9 × 9 diffraction kernel took 13.1 s at 48 × 48) |
|
|
617
|
+
| `motion` yaw 0.01 rad/s (8 temporal samples) | 1.70 s (3.4×) | 8.74 s (3.5×) | Motion blur; cost grows with `sensor.temporalSampleCount` |
|
|
618
|
+
| rotation + `shutter: "rolling"` | 1.79 s (3.6×) | 9.24 s (3.7×) | Rolling-shutter skew of moving content |
|
|
619
|
+
| `motion.shutter: "rolling"` (still camera) | 0.47 s (0.9×) | 2.51 s (1.0×) | Per-row exposure windows; no cost alone |
|
|
620
|
+
| `output.format: "png"` | 0.50 s (1.0×) | 2.63 s (1.1×) | PNG file bytes; encode 16 ms at 128 |
|
|
621
|
+
| `output.format: "jpeg"` | 0.52 s (1.0×) | 2.59 s (1.0×) | Baseline JPEG; encode 45 ms at 128 |
|
|
622
|
+
| `output.format: "dng"` | 0.50 s (1.0×) | 2.27 s (0.9×) | Lossless CFA DNG of the RAW; encode 4 ms at 128 |
|
|
623
|
+
| `output.highlightMode: "reconstruct"` | 0.50 s (1.0×) | 2.20 s (0.9×) | Near-clipped highlights blend smoothly toward neutral instead of clipping per channel; no measurable cost |
|
|
624
|
+
| `output.whiteBalance: "auto"` | 0.77 s (1.6×) | 4.76 s (1.9×) | Grey-world white balance; renders the RAW a second time (2.24 s at 128) |
|
|
625
|
+
|
|
626
|
+
Ratios below about 1.1× are within the run-to-run spread (the three default runs at 128 × 128 took
|
|
627
|
+
2.23–2.50 s). The radiance work dominates: options that multiply the scene queries per site
|
|
628
|
+
(anti-aliasing spots, pupil rays, PSF taps, temporal samples) multiply the run time by about the same
|
|
629
|
+
factor, and output-side options cost almost nothing.
|
|
630
|
+
|
|
631
|
+
### Measuring cost on the user's device
|
|
632
|
+
|
|
633
|
+
From 2.1 every result says what ran and how long each stage took, so an application can decide on the
|
|
634
|
+
user's own device which options to offer:
|
|
635
|
+
|
|
636
|
+
- `diagnostics.options` lists the opt-ins that changed the work of the run, in the fixed order of
|
|
637
|
+
`SIMULATE_CAPTURE_COST_OPTIONS` (for example `["lens.diffraction", "sensor.antiAliasing",
|
|
638
|
+
"output.demosaic.ahd", "output.format.png"]`). It is empty when every default is used.
|
|
639
|
+
- `diagnostics.timingsMs` has `raw`, `whiteBalance` (the second RAW pass of `whiteBalance: "auto"`),
|
|
640
|
+
`development` (includes DNG writing), `jpeg` and `total` in whole milliseconds, and, where the stage
|
|
641
|
+
ran, `diffraction` (the object-space convolution, part of `raw`), `demosaic` (the AHD demosaic, part
|
|
642
|
+
of `development`) and `encode` (PNG, JPEG or DNG serialization), with sub-millisecond resolution where
|
|
643
|
+
the host has `performance.now()`.
|
|
644
|
+
|
|
645
|
+
Timings change from run to run, but nothing else does: they never enter the scene digest, the RAW,
|
|
646
|
+
the samples or the encoded files, and a test checks that two runs differ only in `timingsMs`.
|
|
647
|
+
|
|
648
|
+
```ts
|
|
649
|
+
import { simulateCapture } from "@photivra/engine";
|
|
650
|
+
|
|
651
|
+
const probe = await simulateCapture({ scene: { preset: "slanted-edge" }, output: { width: 32, height: 32, demosaic: "ahd" } });
|
|
652
|
+
console.log(probe.diagnostics.options, probe.diagnostics.timingsMs.total, probe.diagnostics.timingsMs.demosaic);
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
## Large images
|
|
656
|
+
|
|
657
|
+
By default a capture is at most 4094 × 4094 output pixels and every full-resolution stage admits at most
|
|
658
|
+
24 000 000 pixels (PNG: 100 000 000). Large images are slow and memory-hungry, so from 2.2 they are an
|
|
659
|
+
opt-in that the application decides on, for example after checking the device:
|
|
660
|
+
|
|
661
|
+
- `simulateCapture`: `output.maximumPixels`, the most native sensor sites (output plus the demosaic margin
|
|
662
|
+
on each side) the capture may allocate. A 12 252 × 8 168 output with the default margin needs
|
|
663
|
+
12 254 × 8 170 = 100 115 180; passing `NATIVE_RAW_LIMITS.ceilingPixels` is simplest. Each side may then
|
|
664
|
+
reach 16 384 sites including the margin. `output.demosaic: "ahd"` still admits at most 2^26 = 67 108 864
|
|
665
|
+
native sites, checked before any work.
|
|
666
|
+
- The stages directly: `maximumPixels` on `calculateNativeRawPlan` / `createNativeRawTask` (and so on every
|
|
667
|
+
native environment and development task built from that input), `createNativeCaptureSdrTask`,
|
|
668
|
+
`calculateIrradianceConvolution`, `calculateTemporalExposureSum`, `createPrintJpegTask` and
|
|
669
|
+
`serializePngImage`; `maximumRegionSites` on `simulatePackedSensorRaw`; `maximumQueries` on
|
|
670
|
+
`calculateChartRadianceBatch`.
|
|
671
|
+
|
|
672
|
+
Each value must be an integer from 1 to the stage's ceiling (`ceilingPixels`, `ceilingRegionSites`,
|
|
673
|
+
`ceilingQueries`), all 2^27 = 134 217 728 = 16 384 × 8 192. That covers 12 252 × 8 168 (100.07 MP) and
|
|
674
|
+
11 552 × 8 664 (100.09 MP) sensors plus their margin with about a third to spare. It is also the size at
|
|
675
|
+
which the pipeline's largest single array, the interleaved float64 RGB of the development (24 bytes per
|
|
676
|
+
pixel), reaches 3 GiB: JavaScript engines cap an `ArrayBuffer` at about 4 GiB or less, so a higher ceiling
|
|
677
|
+
could not be honoured anyway. The per-side limit (16 384) and every byte budget (`maximumOutputBytes` and
|
|
678
|
+
similar) still apply. A raised native RAW limit raises the native environment whole-event query budget
|
|
679
|
+
ceiling in proportion (2 000 000 000 per 24 000 000 pixels), and a raised Print JPEG limit raises its
|
|
680
|
+
`maximumEncodedBytes` ceiling the same way (256 000 000 bytes per 24 000 000 pixels). Leaving the inputs out
|
|
681
|
+
changes nothing: requests, plans and output bytes stay as they were.
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
import { NATIVE_RAW_LIMITS, simulateCapture } from "@photivra/engine";
|
|
685
|
+
|
|
686
|
+
// The same capture with large images allowed; at 16 × 16 the output is identical to the default.
|
|
687
|
+
const large = await simulateCapture({ scene: { preset: "slanted-edge" }, output: { width: 16, height: 16, maximumPixels: NATIVE_RAW_LIMITS.ceilingPixels } });
|
|
688
|
+
console.log(large.plan.maximumPixels, large.plan.pixelCount);
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**Measured cost.** One fresh Node process per stage and size (Node 24.21.0, 4-vCPU Intel Xeon 2.80 GHz,
|
|
692
|
+
Linux, one run each, single thread). Synthetic inputs: a gradient with pseudo-random noise for the
|
|
693
|
+
encoders, repeating patterns elsewhere; the PNG and JPEG times depend on the image content. *Peak
|
|
694
|
+
memory* is the process's peak resident memory minus the idle runtime (about 125 MB), per pixel; it
|
|
695
|
+
includes the stage's input and output arrays. Not a performance claim, but it shows how the cost grows.
|
|
696
|
+
|
|
697
|
+
| Stage (as measured) | 1 MP | 24 MP | 100.07 MP | Per MP | Peak memory per pixel |
|
|
698
|
+
| --- | --- | --- | --- | --- | --- |
|
|
699
|
+
| Irradiance convolution, radius 2 (25 taps), float32 in | 0.32 s | 6.5 s | 27.7 s | 0.28 s | 12 B (4 in, 8 out) |
|
|
700
|
+
| Temporal exposure sum, 8 nodes | 0.56 s | 9.7 s | 42.0 s | 0.42 s | 12 B (4 in, 8 out) |
|
|
701
|
+
| Packed sensor RAW (`simulatePackedSensorRaw`) | 0.60 s | 11.9 s | 48.2 s | 0.48 s | 11 B measured; the all-zero dark plane was never touched, so count 16 B of input plus the output |
|
|
702
|
+
| Chart radiance batch, one wavelength | 0.13 s | 2.6 s | 13.0 s | 0.13 s | 17 B (9 in, 8 out) |
|
|
703
|
+
| PNG, 8-bit sRGB | 0.31 s | 2.7 s | 22.7 s | 0.23 s | 8 B (3 in) |
|
|
704
|
+
| PNG, 16-bit sRGB | 0.57 s | 5.1 s | 40.7 s | 0.41 s | 15 B (6 in) |
|
|
705
|
+
| Print JPEG, 1:1, quantization step 2 | 0.84 s | 19.5 s | 69.7 s | 0.70 s | 10 B (3 in; 2.6–2.9 B out) |
|
|
706
|
+
| Native capture SDR, 8-bit (tiles from the caller) | 0.07 s | 1.6 s | 5.8 s | 0.06 s | 4 B (3 out) |
|
|
707
|
+
| Native RAW DNG writer | 0.07 s | 1.7 s | 6.4 s | 0.06 s | 17 B (7 in, 2 out) |
|
|
708
|
+
| Native RAW development to 8-bit sRGB | 0.34 s | 5.2 s | 20.5 s | 0.21 s | 10 B (7 in, 3 out) |
|
|
709
|
+
| `simulateCapture`, default options, slanted edge, sRGB 8-bit | 129 s | — (16 MP: 1 973 s) | about 3.4 h (extrapolated) | 123–129 s | 34 B at 16 MP (fixed part about 360 MB) |
|
|
710
|
+
|
|
711
|
+
**What this means for a 100 MP capture.** The full capture was measured at 0.26, 1, 4 and 16 MP: 133, 129, 125 and 123 s per MP, almost all of it
|
|
712
|
+
the per-site RAW simulation. At that rate a 100 MP capture with default options takes about 12 300 s
|
|
713
|
+
(3.4 h) on one core of this host; the options in [Cost of each option](#cost-of-each-option) multiply
|
|
714
|
+
that. The 100 MP time is extrapolated, not measured. Memory: about 360 MB of fixed working state, then a
|
|
715
|
+
peak during development of about 34 bytes per pixel (7 B packed RAW, 24 B float64 linear RGB, 3 B 8-bit
|
|
716
|
+
samples), measured 541 MB above the idle runtime at 16 MP, so about 3.4 GB at 100 MP. JPEG and PNG
|
|
717
|
+
encoding add their own rows above; `whiteBalance: "auto"` renders the RAW twice.
|
|
718
|
+
|
|
719
|
+
Browsers are tighter than Node: a tab may have a few GB at most, a single `ArrayBuffer` may be limited
|
|
720
|
+
to 2–4 GB, and mobile browsers kill tabs earlier. A 100 MP capture can therefore fail in a browser with a
|
|
721
|
+
`RangeError` or an out-of-memory crash even where it runs in Node. Run large captures in a Web Worker,
|
|
722
|
+
pass `signal` so they can be cancelled, and offer them only on devices that can afford them.
|
|
723
|
+
|
|
457
724
|
## Not available
|
|
458
725
|
|
|
459
726
|
The facade offers no atmospheric haze or polarizer (see [Optics](#optics)), flash, video, HDR brackets
|
|
@@ -462,6 +729,11 @@ provided, for rights reasons.
|
|
|
462
729
|
|
|
463
730
|
## Sources
|
|
464
731
|
|
|
732
|
+
- R. Palum, "Anti-Aliasing Filter Analysis for Digital Cameras", Proc. IS&T International Congress of
|
|
733
|
+
Imaging Science (ICIS '06), 2006, free at
|
|
734
|
+
<https://www.imaging.org/common/uploaded%20files/pdfs/Papers/2006/ICIS-0-736/33683.pdf>
|
|
735
|
+
(birefringent quartz four-spot filter; one pixel wide in each direction cuts off at Nyquist; its MTF
|
|
736
|
+
is the modulus of a cosine that returns to one at the sampling frequency).
|
|
465
737
|
- CIPA DC-004-Translation-2020, *Sensitivity of Digital Cameras*, free at
|
|
466
738
|
<https://www.cipa.jp/e/std/std-sec.html> (standard output sensitivity S = 10/H, §3.2; reference
|
|
467
739
|
level tied to 18 % grey, §5.2 and Explanation 1, 2.2; focal-plane exposure H = 0.65·L·t/N², §5.5
|
package/docs/USAGE.md
CHANGED
|
@@ -1911,6 +1911,8 @@ console.log(metrics.value.sampling.pitchYMicrometers); // 6
|
|
|
1911
1911
|
|
|
1912
1912
|
The returned X/Y sampling pitches are the geometric spacing of image samples. They are not photodiode active area, fill factor or a photon-collection model, and must not be used as those quantities.
|
|
1913
1913
|
|
|
1914
|
+
Passing such a pitch to `simulateCapture()` as `sensor.pixelPitchMicrometers` is a supported use, but the photon model then comes from the facade, not from this pitch: the facade declares its own sensor with sites at that spacing and a square sensitive aperture of the same side (fill factor 1, the `sensor.fillFactor` assumption), and scales its ISO readout with that aperture area (see [Quick start: ISO and pixel size](QUICK_START.md#iso-and-pixel-size)). A real sensor of the same pitch may have a smaller sensitive area, microlenses and a different full-well capacity.
|
|
1915
|
+
|
|
1914
1916
|
The existing `calculatePixelPitch()` API remains available for callers that only need horizontal pitch from sensor width and horizontal pixel count.
|
|
1915
1917
|
|
|
1916
1918
|
## Sensor architecture metadata
|
|
@@ -2428,7 +2430,7 @@ The site-center lattice is a distinct physical registration contract:
|
|
|
2428
2430
|
- coordinates are `native-sensor-physical`: optical-axis origin, +X right, +Y down;
|
|
2429
2431
|
- `pitchXMicrometers` and `pitchYMicrometers` are explicit evidence-backed site-center spacings;
|
|
2430
2432
|
- the first site's center is explicitly registered relative to the physical imaging area's top-left edge;
|
|
2431
|
-
- the full declared lattice must fit inside the supplied physical imaging area.
|
|
2433
|
+
- the full declared lattice, and the sensitive aperture of every site, must fit inside the supplied physical imaging area. The test allows 8 ε (about 1.8 × 10⁻¹⁵) of the imaging extent for floating-point rounding: an imaging width given as `n · pitch / 1000` mm and a far aperture edge computed as `pitch/2 + (n − 1)·pitch + pitch/2` µm reach the same exact value by different rounded operations and can differ in the last bit. Any larger excess is rejected.
|
|
2432
2434
|
|
|
2433
2435
|
Photivra does **not** derive this site pitch or origin from `NativeImageRaster`. The native effective raster and the colour-site lattice already have a structural binding, but physical placement is a separate fact.
|
|
2434
2436
|
|
|
@@ -29,10 +29,10 @@ This call is synchronous; any supplied provider must follow its explicit synchro
|
|
|
29
29
|
|
|
30
30
|
## NATIVE_RAW_LIMITS
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
`maximumPixels` is the default pixel limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`.
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
|
-
NATIVE_RAW_LIMITS = Object.freeze({ maximumPixels: 24_000_000, maximumDimension: 16_384,
|
|
35
|
+
NATIVE_RAW_LIMITS = Object.freeze({ maximumPixels: 24_000_000, ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS, maximumDimension: 16_384,
|
|
36
36
|
tileWidth: 256, tileHeight: 1, outputBytesPerPixel: 7 })
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -76,6 +76,13 @@ export interface NativeRawInput {
|
|
|
76
76
|
maximumOutputBytes: number;
|
|
77
77
|
/** Optional tile width for execution, in native sites, 1–256. Default 256. It does not affect sampling. */
|
|
78
78
|
tileWidth?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Optional opt-in pixel limit for large sensors: an integer from 1 to `NATIVE_RAW_LIMITS.ceilingPixels`.
|
|
81
|
+
* Default `NATIVE_RAW_LIMITS.maximumPixels` (24 000 000). `maximumOutputBytes` still applies. A raised limit
|
|
82
|
+
* also raises the native environment tasks' whole-event query budget ceiling in proportion
|
|
83
|
+
* (2 000 000 000 per 24 000 000 pixels). It does not affect sampling.
|
|
84
|
+
*/
|
|
85
|
+
maximumPixels?: number;
|
|
79
86
|
}
|
|
80
87
|
```
|
|
81
88
|
|
|
@@ -115,6 +122,8 @@ export interface NativeRawPlan {
|
|
|
115
122
|
tileCount: number;
|
|
116
123
|
/** Present only when an execution tile width was given. Otherwise the plan keeps its earlier shape. */
|
|
117
124
|
tileWidth?: number;
|
|
125
|
+
/** Present only when a pixel limit was given. Otherwise the plan keeps its earlier shape. */
|
|
126
|
+
maximumPixels?: number;
|
|
118
127
|
upstreamRadiometryVerified: false;
|
|
119
128
|
seedSchedule: "murmur3-capture-seed-native-index-stream-v2";
|
|
120
129
|
}
|
|
@@ -14,11 +14,12 @@ This call is synchronous; any supplied provider must follow its explicit synchro
|
|
|
14
14
|
|
|
15
15
|
## PACKED_SENSOR_RAW_LIMITS
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
`maximumRegionSites` is the default region limit; a request may raise it with its own `maximumRegionSites`, up to `ceilingRegionSites`.
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
20
|
PACKED_SENSOR_RAW_LIMITS = Object.freeze({
|
|
21
21
|
maximumRegionSites: 24_000_000,
|
|
22
|
+
ceilingRegionSites: FULL_RESOLUTION_CEILING_PIXELS,
|
|
22
23
|
maximumNativeSites: 0xffffffff,
|
|
23
24
|
maximumDimension: 65_535,
|
|
24
25
|
maximumAdcBitDepth: 16,
|
|
@@ -103,6 +104,11 @@ export interface PackedSensorRawInput {
|
|
|
103
104
|
fixedPatternNoise?: PackedSensorFixedPatternNoise;
|
|
104
105
|
/** Optional (2.0), at most 8. */
|
|
105
106
|
additionalChargeComponents?: readonly PackedSensorAdditionalChargeComponent[];
|
|
107
|
+
/**
|
|
108
|
+
* Optional opt-in region limit for large sensors: an integer from 1 to `PACKED_SENSOR_RAW_LIMITS.ceilingRegionSites`.
|
|
109
|
+
* Default `PACKED_SENSOR_RAW_LIMITS.maximumRegionSites` (24 000 000). It does not change the result.
|
|
110
|
+
*/
|
|
111
|
+
maximumRegionSites?: number;
|
|
106
112
|
}
|
|
107
113
|
```
|
|
108
114
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# core/pixel-limits.ts public contracts
|
|
2
|
+
|
|
3
|
+
The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
|
|
4
|
+
|
|
5
|
+
## FULL_RESOLUTION_CEILING_PIXELS
|
|
6
|
+
|
|
7
|
+
Hard ceiling of an opt-in raised pixel limit: 2^27 = 134 217 728 pixels (16 384 × 8 192).
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
FULL_RESOLUTION_CEILING_PIXELS = 134_217_728
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## FULL_RESOLUTION_DEFAULT_PIXELS
|
|
14
|
+
|
|
15
|
+
Default pixel limit of the full-resolution stages.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
FULL_RESOLUTION_DEFAULT_PIXELS = 24_000_000
|
|
19
|
+
```
|
package/docs/api/core-version.md
CHANGED
|
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
|
|
|
11
11
|
own versions. Release checks confirm it matches package.json and the packed package.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
ENGINE_API_VERSION = "2.
|
|
14
|
+
ENGINE_API_VERSION = "2.2.0" as const
|
|
15
15
|
```
|
package/docs/api/exports.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"packageVersion": "2.
|
|
3
|
-
"engineApiVersion": "2.
|
|
2
|
+
"packageVersion": "2.2.0",
|
|
3
|
+
"engineApiVersion": "2.2.0",
|
|
4
4
|
"exports": [
|
|
5
5
|
{
|
|
6
6
|
"name": "AbsoluteUncertaintyEstimate",
|
|
@@ -2122,6 +2122,16 @@
|
|
|
2122
2122
|
"source": "src/color/white-balance.ts",
|
|
2123
2123
|
"kind": "type"
|
|
2124
2124
|
},
|
|
2125
|
+
{
|
|
2126
|
+
"name": "createDiffractionLimitedPsfProfile",
|
|
2127
|
+
"source": "src/optics/diffraction-psf-profile.ts",
|
|
2128
|
+
"kind": "runtime"
|
|
2129
|
+
},
|
|
2130
|
+
{
|
|
2131
|
+
"name": "CreateDiffractionLimitedPsfProfileInput",
|
|
2132
|
+
"source": "src/optics/diffraction-psf-profile.ts",
|
|
2133
|
+
"kind": "type"
|
|
2134
|
+
},
|
|
2125
2135
|
{
|
|
2126
2136
|
"name": "createDurandDorseyToneMappingTask",
|
|
2127
2137
|
"source": "src/processing/tone-mapping.ts",
|
|
@@ -2437,6 +2447,26 @@
|
|
|
2437
2447
|
"source": "src/optics/depth-of-field.ts",
|
|
2438
2448
|
"kind": "type"
|
|
2439
2449
|
},
|
|
2450
|
+
{
|
|
2451
|
+
"name": "DIFFRACTION_LIMITED_PSF_PROFILE_LIMITS",
|
|
2452
|
+
"source": "src/optics/diffraction-psf-profile.ts",
|
|
2453
|
+
"kind": "runtime"
|
|
2454
|
+
},
|
|
2455
|
+
{
|
|
2456
|
+
"name": "DIFFRACTION_LIMITED_PSF_PROFILE_MODEL",
|
|
2457
|
+
"source": "src/optics/diffraction-psf-profile.ts",
|
|
2458
|
+
"kind": "runtime"
|
|
2459
|
+
},
|
|
2460
|
+
{
|
|
2461
|
+
"name": "DiffractionLimitedPsfProfile",
|
|
2462
|
+
"source": "src/optics/diffraction-psf-profile.ts",
|
|
2463
|
+
"kind": "type"
|
|
2464
|
+
},
|
|
2465
|
+
{
|
|
2466
|
+
"name": "DiffractionLimitedPsfWavelength",
|
|
2467
|
+
"source": "src/optics/diffraction-psf-profile.ts",
|
|
2468
|
+
"kind": "type"
|
|
2469
|
+
},
|
|
2440
2470
|
{
|
|
2441
2471
|
"name": "DigitalGeometricTransform",
|
|
2442
2472
|
"source": "src/output/geometric-transforms.ts",
|
|
@@ -3032,6 +3062,16 @@
|
|
|
3032
3062
|
"source": "src/optics/front-of-lens-filter.ts",
|
|
3033
3063
|
"kind": "type"
|
|
3034
3064
|
},
|
|
3065
|
+
{
|
|
3066
|
+
"name": "FULL_RESOLUTION_CEILING_PIXELS",
|
|
3067
|
+
"source": "src/core/pixel-limits.ts",
|
|
3068
|
+
"kind": "runtime"
|
|
3069
|
+
},
|
|
3070
|
+
{
|
|
3071
|
+
"name": "FULL_RESOLUTION_DEFAULT_PIXELS",
|
|
3072
|
+
"source": "src/core/pixel-limits.ts",
|
|
3073
|
+
"kind": "runtime"
|
|
3074
|
+
},
|
|
3035
3075
|
{
|
|
3036
3076
|
"name": "FullAutoExposureModeResolution",
|
|
3037
3077
|
"source": "src/exposure/exposure-mode-resolver.ts",
|
|
@@ -5242,6 +5282,11 @@
|
|
|
5242
5282
|
"source": "src/output/print-contrast-reference.ts",
|
|
5243
5283
|
"kind": "runtime"
|
|
5244
5284
|
},
|
|
5285
|
+
{
|
|
5286
|
+
"name": "PRINT_JPEG_LIMITS",
|
|
5287
|
+
"source": "src/output/print-jpeg.ts",
|
|
5288
|
+
"kind": "runtime"
|
|
5289
|
+
},
|
|
5245
5290
|
{
|
|
5246
5291
|
"name": "PRINT_JPEG_SCHEMA_VERSION",
|
|
5247
5292
|
"source": "src/output/print-jpeg.ts",
|
|
@@ -7692,6 +7737,11 @@
|
|
|
7692
7737
|
"source": "src/sensor/signal-noise.ts",
|
|
7693
7738
|
"kind": "type"
|
|
7694
7739
|
},
|
|
7740
|
+
{
|
|
7741
|
+
"name": "SIMULATE_CAPTURE_COST_OPTIONS",
|
|
7742
|
+
"source": "src/facade/simulate-capture.ts",
|
|
7743
|
+
"kind": "runtime"
|
|
7744
|
+
},
|
|
7695
7745
|
{
|
|
7696
7746
|
"name": "SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX",
|
|
7697
7747
|
"source": "src/facade/simulate-capture.ts",
|
|
@@ -7722,6 +7772,11 @@
|
|
|
7722
7772
|
"source": "src/facade/simulate-capture.ts",
|
|
7723
7773
|
"kind": "type"
|
|
7724
7774
|
},
|
|
7775
|
+
{
|
|
7776
|
+
"name": "SimulateCaptureCostOption",
|
|
7777
|
+
"source": "src/facade/simulate-capture.ts",
|
|
7778
|
+
"kind": "type"
|
|
7779
|
+
},
|
|
7725
7780
|
{
|
|
7726
7781
|
"name": "SimulateCaptureDemosaic",
|
|
7727
7782
|
"source": "src/facade/simulate-capture.ts",
|
|
@@ -7762,6 +7817,11 @@
|
|
|
7762
7817
|
"source": "src/facade/simulate-capture.ts",
|
|
7763
7818
|
"kind": "type"
|
|
7764
7819
|
},
|
|
7820
|
+
{
|
|
7821
|
+
"name": "SimulateCaptureSensorAntiAliasing",
|
|
7822
|
+
"source": "src/facade/simulate-capture.ts",
|
|
7823
|
+
"kind": "type"
|
|
7824
|
+
},
|
|
7765
7825
|
{
|
|
7766
7826
|
"name": "SimulateCaptureWhiteBalance",
|
|
7767
7827
|
"source": "src/facade/simulate-capture.ts",
|