@photivra/engine 2.4.1 → 2.5.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 +16 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +5 -1
- package/dist/facade/simulate-capture.js +15 -2
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/subject-framing.d.ts +62 -0
- package/dist/facade/subject-framing.js +62 -19
- package/dist/facade/subject-framing.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.d.ts +51 -2
- package/dist/optics/diffraction-psf-profile.js +116 -3
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/docs/API_REFERENCE.md +4 -1
- package/docs/QUICK_START.md +90 -5
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +17 -2
- package/docs/api/facade-simulate-capture.md +5 -1
- package/docs/api/facade-subject-framing.md +67 -0
- package/docs/api/optics-diffraction-psf-profile.md +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2.
|
|
3
|
+
## 2.5.0 — unreleased (prepared October 18, 2026)
|
|
4
|
+
|
|
5
|
+
A minor release for whole-frame views and chart framing: `simulateCapture` accepts pixel pitches up to 1000 µm, so a whole sensor frame can be rendered at a small output size with the real sensor's prime, minimum focus and framing, and `calculateChartFraming` reports how much of the frame a chart covers at any distance. Both are additive: every request that 2.4.1 accepted gives byte-identical results (the engine version recorded in captures aside). Root exports grow from 1,714 to 1,717 symbols; none is removed.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Whole-frame views at large pixel pitches.** `simulateCapture` `sensor.pixelPitchMicrometers` now accepts 0.5–1000 µm (0.5–100 before). Pitch = sensor width ÷ output width renders the whole frame at a small output size (36 mm at 48 pixels is 750 µm, 44 mm at 48 pixels 917 µm), so `lens.prime` (crop factor, minimum focus), `calculateSubjectDistanceForFraming` and `chartSize` match the real sensor. Above 100 µm the new assumption `sensor.wholeFrameView` records the frame size and states that the noise is that of a binned, downsized view: each pixel's shot noise equals the real photosites in its square summed, while read noise and dark current are applied once per output pixel. Tested at 100–1000 µm: brightness against 6 µm at area ratios up to 27 778 (each CFA channel's RAW mean within half a code, sRGB mean within one, identical saturation), photon transfer at ISO 12 800 within 12 %, diffraction kernels far below a pixel, the four-spot filter, AHD, 2 × 2 and 4 × 4 box apertures, rotation blur and a rolling shutter (each matching the same framing at 1/100 of the pitch), byte-identical region tiles, and a clean `unsupported-float32-domain` from the `webgpu-f32` sensor stage above its 2^24 e⁻ capacity (see the quick start's "Whole-frame views (2.5)").
|
|
10
|
+
- **`calculateChartFraming`: chart coverage at any subject distance.** `calculateChartFraming({ sensor, focalLengthMm | equivalentFocalLengthMm, chart, subjectDistanceM, focusDistanceM? })` returns the thin-lens magnification v/d (v at the focus distance, default the subject distance), the framed plane size, the chart's coverage of the frame width and height (above 1 when cropped), its image size in millimetres, the generic prime of that equivalent focal length and the minimum-focus check (`belowMinimumFocus`), with assumptions. It is the framing `simulateCapture` uses: tested equal to `result.scene.chart` with `chartSize` (within 1e-12 relative, including a separate focus distance) and to coverage 1 on the fitted side at `calculateSubjectDistanceForFraming`'s distance. New `CalculateChartFramingInput` and `ChartFraming`. Root exports grow from 1,714 to 1,717 symbols; none is removed.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Diffraction cells far larger than the Airy pattern are integrated in real space.** `createDiffractionLimitedPsfProfile` (and so `lens.diffraction`) integrates cells with β = pitch/(λ·N_w) ≥ 192 from the Airy encircled energy 1 − J₀² − J₁² (Hankel expansion, DLMF 10.17.3) instead of the frequency-domain quadrature, whose work grows as β²: a 48 × 32 view of a 44 mm frame at f/1.4 with diffraction takes 1.0 s instead of 296 s. Same model; the two methods agree within 2e-12 at β = 192–400. `quadrature.method` gains `"real-space-encircled-energy-gauss-legendre"` and `"frequency-domain-and-real-space"`, with `quadrature.realSpaceWavelengthsNm` listing the real-space wavelengths; profiles below the threshold are unchanged. Every `simulateCapture` pitch up to 100 µm stays below it.
|
|
15
|
+
|
|
16
|
+
Requests at 0.5–100 µm are byte-identical to 2.4.1 (the hash locks and the facade fingerprints of 15 default-path results).
|
|
17
|
+
|
|
18
|
+
## 2.4.1 — published October 18, 2026
|
|
4
19
|
|
|
5
20
|
A republication of 2.4.0. 2.4.0 was tagged and released on GitHub on October 18, 2026 but never reached npm: the Publish npm workflow's verify job hit its 10-minute limit during the coverage gate on both attempts and was cancelled before the publish job ran. 2.4.1 carries the 2.4.0 contents unchanged (caller text metadata in JPEG, PNG and DNG outputs, `output.metadata`, `serializeNativeRawDng` `textMetadata` and `OutputTextMetadata`; see the 2.4.0 entry below) plus the publish-workflow fix. Apart from the engine version that captures record, every request, plan, RAW, image, encoded file and hash lock is byte-identical to 2.4.0, and without `output.metadata` to 2.3.0. Root exports stay at 1,714 symbols; none is added or removed.
|
|
6
21
|
|
package/CITATION.cff
CHANGED
|
@@ -9,7 +9,7 @@ abstract: "An open TypeScript engine for the science and mathematics of photogra
|
|
|
9
9
|
repository-code: "https://github.com/photivra/photivra"
|
|
10
10
|
url: "https://photivra.com"
|
|
11
11
|
license: "Apache-2.0"
|
|
12
|
-
version: "2.
|
|
12
|
+
version: "2.5.0"
|
|
13
13
|
keywords:
|
|
14
14
|
- photography
|
|
15
15
|
- camera optics
|
package/README.md
CHANGED
|
@@ -32,10 +32,10 @@ The source is licensed under Apache-2.0.
|
|
|
32
32
|
|
|
33
33
|
## Quick start
|
|
34
34
|
|
|
35
|
-
Install the published ESM package below. The [2.
|
|
35
|
+
Install the published ESM package below. The [2.5.0 release record](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_2_5_0.md) and [changelog](https://github.com/Photivra/photivra/blob/main/CHANGELOG.md) describe the current release:
|
|
36
36
|
|
|
37
37
|
```sh
|
|
38
|
-
npm install @photivra/engine@2.
|
|
38
|
+
npm install @photivra/engine@2.5.0
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Simulate a photograph of a test chart with a generic camera, then read the developed sRGB pixels:
|
|
@@ -75,7 +75,7 @@ console.log(horizontal.provenance);
|
|
|
75
75
|
|
|
76
76
|
Start with the [Developer Guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Then use the [Usage Guide](https://github.com/Photivra/photivra/blob/main/docs/USAGE.md) and the [API reference](https://github.com/Photivra/photivra/blob/main/docs/API_REFERENCE.md), which is generated from the package exports.
|
|
77
77
|
|
|
78
|
-
Repository `main` may describe newer, unreleased work. For the stable contract, use the docs bundled in the published package, or the exact `v2.
|
|
78
|
+
Repository `main` may describe newer, unreleased work. For the stable contract, use the docs bundled in the published package, or the exact `v2.5.0` tag.
|
|
79
79
|
|
|
80
80
|
## Documentation
|
|
81
81
|
|
|
@@ -363,13 +363,13 @@ The application can then use those results, while the photography calculations t
|
|
|
363
363
|
|
|
364
364
|
## Status
|
|
365
365
|
|
|
366
|
-
- Repository package version: `2.
|
|
367
|
-
- Engine API contract: `2.
|
|
366
|
+
- Repository package version: `2.5.0` (release candidate prepared October 18, 2026; publication is separate)
|
|
367
|
+
- Engine API contract: `2.5.0`
|
|
368
368
|
- Versioned generic tier reference assets: [GENERIC_TIER_PRESETS.md](https://github.com/Photivra/photivra/blob/main/docs/GENERIC_TIER_PRESETS.md).
|
|
369
369
|
- Composed POC simulation API contract: `1.0.0`
|
|
370
370
|
- Stability: 1.0 root-package compatibility policy; bounded scientific models and independent POC contract
|
|
371
371
|
|
|
372
|
-
From 1.0.1, the package version and `ENGINE_API_VERSION` are equal. New captures/plans record 2.
|
|
372
|
+
From 1.0.1, the package version and `ENGINE_API_VERSION` are equal. New captures/plans record 2.5.0; existing archives keep their original creator identities. Schema, model and POC contract versions stay independent.
|
|
373
373
|
|
|
374
374
|
A breaking change to the root contracts now needs a new package major version. Additive and corrective changes follow [compatibility and migration](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_1_3_0.md). A 1.0 package is not a claim of calibrated physical accuracy.
|
|
375
375
|
|
package/SECURITY.md
CHANGED
|
@@ -4,7 +4,7 @@ Please do not open a public issue that contains vulnerability details.
|
|
|
4
4
|
|
|
5
5
|
## Supported versions
|
|
6
6
|
|
|
7
|
-
The published 1.0.0 release started the 1.x root-package compatibility line, and 2.0.0 started the 2.x line. The current release is **2.
|
|
7
|
+
The published 1.0.0 release started the 1.x root-package compatibility line, and 2.0.0 started the 2.x line. The current release is **2.4.1**, published on October 18, 2026 (see [CHANGELOG.md](CHANGELOG.md)).
|
|
8
8
|
|
|
9
9
|
Security fixes target the current maintained development and release line. Do not assume that older 1.x releases, snapshots or pre-1.0 releases will receive fixes.
|
|
10
10
|
|
package/dist/core/version.d.ts
CHANGED
package/dist/core/version.js
CHANGED
package/dist/core/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAgB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAgB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.5.0\" as const;\n"]}
|
|
@@ -239,7 +239,11 @@ export interface SimulateCaptureOptions {
|
|
|
239
239
|
/** Camera motion and shutter type. Default: still camera, global electronic shutter. */
|
|
240
240
|
motion?: SimulateCaptureMotion;
|
|
241
241
|
sensor?: {
|
|
242
|
-
/**
|
|
242
|
+
/**
|
|
243
|
+
* Pixel pitch in micrometres, 0.5–1000. Default 6. The simulated sensor is output width × pitch wide. 2.5: above 100 µm the
|
|
244
|
+
* capture is a downsized view of a whole frame (for example 36 mm / 48 pixels = 750 µm), recorded in the assumption
|
|
245
|
+
* `sensor.wholeFrameView`: brightness is unchanged and the noise is that of a binned view, not of the real sensor's pixels.
|
|
246
|
+
*/
|
|
243
247
|
pixelPitchMicrometers?: number;
|
|
244
248
|
/** Maximum spectral quadrature subinterval in nm. Default 20. */
|
|
245
249
|
spectralStepNanometers?: number;
|
|
@@ -120,6 +120,10 @@ export const SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX = Math.PI * sosReferenceLu
|
|
|
120
120
|
* aperture with fill factor 1. At other areas the facade scales the electron-domain readout values by
|
|
121
121
|
* area / this reference, so the same focal-plane exposure H gives the same output code (see `isoAreaScaledReadout`).
|
|
122
122
|
*/
|
|
123
|
+
/** 2.5: the largest accepted pixel pitch, µm (44 mm ÷ 48 pixels ≈ 917 µm, a whole full-frame or larger sensor at a small output). */
|
|
124
|
+
const SIMULATE_CAPTURE_PIXEL_PITCH_MAXIMUM_MICROMETERS = 1000;
|
|
125
|
+
/** Pitches above this, µm, are larger than any camera photosite: the capture is a downsized whole-frame view (assumption sensor.wholeFrameView). */
|
|
126
|
+
const SIMULATE_CAPTURE_PHOTOSITE_PITCH_MAXIMUM_MICROMETERS = 100;
|
|
123
127
|
const ISO_REFERENCE_APERTURE_AREA_SQUARE_MICROMETERS = DEFAULTS.pixelPitchMicrometers * DEFAULTS.pixelPitchMicrometers;
|
|
124
128
|
/** Demosaic border, native sites per side. */
|
|
125
129
|
const MARGIN = 1;
|
|
@@ -687,8 +691,8 @@ async function runCapture(options, idealReference) {
|
|
|
687
691
|
fail("output.jpegQuantizationStep applies only to output.format \"jpeg\".");
|
|
688
692
|
const sensorOptions = allowKeys(top.sensor, "sensor", ["pixelPitchMicrometers", "spectralStepNanometers", "temporalSampleCount", "noiseSeed", "antiAliasing", "pixelAperture"]);
|
|
689
693
|
const pitch = positive(pick(sensorOptions, "pixelPitchMicrometers", "sensor.pixelPitchMicrometers", DEFAULTS.pixelPitchMicrometers, "6 µm pixels (about a 24 MP full-frame sensor). The simulated sensor is width × pitch wide, so small images are a central crop of a large sensor, not a downscaled full frame."), "sensor.pixelPitchMicrometers");
|
|
690
|
-
if (pitch < 0.5 || pitch >
|
|
691
|
-
fail("sensor.pixelPitchMicrometers must be 0.5-
|
|
694
|
+
if (pitch < 0.5 || pitch > SIMULATE_CAPTURE_PIXEL_PITCH_MAXIMUM_MICROMETERS)
|
|
695
|
+
fail("sensor.pixelPitchMicrometers must be 0.5-" + SIMULATE_CAPTURE_PIXEL_PITCH_MAXIMUM_MICROMETERS + ".");
|
|
692
696
|
const spectralStep = positive(pick(sensorOptions, "spectralStepNanometers", "sensor.spectralStepNanometers", DEFAULTS.spectralStepNanometers, "Spectral midpoint quadrature over 380-780 nm with subintervals of at most 20 nm."), "sensor.spectralStepNanometers");
|
|
693
697
|
if (spectralStep < 1 || spectralStep > 100)
|
|
694
698
|
fail("sensor.spectralStepNanometers must be 1-100.");
|
|
@@ -947,6 +951,15 @@ async function runCapture(options, idealReference) {
|
|
|
947
951
|
"the aperture area: a design consequence of holding the tier's ISO rating and highlight headroom fixed, not a measured full-well model (published sensor data grow less than " +
|
|
948
952
|
"proportionally with area across technologies).");
|
|
949
953
|
}
|
|
954
|
+
// Whole-frame views (2.5): a pitch above 100 µm is larger than any camera photosite, so the capture is a downsized view of a frame width × pitch wide.
|
|
955
|
+
if (pitch > SIMULATE_CAPTURE_PHOTOSITE_PITCH_MAXIMUM_MICROMETERS) {
|
|
956
|
+
assume("sensor.wholeFrameView", "model", { pixelPitchMicrometers: pitch, frameWidthMm: width * pitch / 1000, frameHeightMm: height * pitch / 1000 }, "A pixel pitch above 100 µm is larger than any camera photosite, so each output pixel stands for a " + pitch + " µm square of a " + width * pitch / 1000 + " × " + height * pitch / 1000 +
|
|
957
|
+
" mm frame: a downsized view of the whole frame, with the framing, crop factor, generic prime and minimum focus of that frame. Its signal is the charge of the whole square " +
|
|
958
|
+
"(sensor.isoAreaNormalization), so its shot noise is that of all the real photosites in the square summed or averaged (Poisson counts add), and the image brightness is the " +
|
|
959
|
+
"same as at any other pitch. Read noise and dark current are applied once per output pixel, not once per real photosite, so in deep shadows the noise is lower than a real " +
|
|
960
|
+
"sensor's binned output. This is the noise of a binned, downsized view, not the real sensor's per-pixel noise. A point pixel aperture samples one point of each square; " +
|
|
961
|
+
"use sensor.pixelAperture box (or sensor.antiAliasing) to average scene detail finer than the pitch.");
|
|
962
|
+
}
|
|
950
963
|
assume("sensor.darkCurrent", "model", { electronsPerSecond: DARK_CURRENT_ELECTRONS_PER_SECOND, temperatureC: OPERATING_TEMPERATURE_C }, "Synthetic uniform dark current of 1 e⁻/s per site at 20 °C (the tier presets declare none); dark shot noise follows from it.");
|
|
951
964
|
assume("sensor.spectralResponse", "model", { kind: "colorimetric-cmf-eqe", peakScale: EQE_SCALE }, "Idealized colorimetric sensor: channel EQE = a·{x̄, ȳ, z̄}(λ)·555/λ (the engine's analytic CIE 1931 fit), so RAW channels are proportional to CIE XYZ (Luther–Ives) and the camera matrix is diagonal. Not a real camera's response; CMF air wavelengths are used as vacuum wavelengths (difference about 0.03 %).");
|
|
952
965
|
if (boxAperture) {
|