@photivra/engine 2.8.0 → 2.9.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 +92 -5
- package/CITATION.cff +1 -1
- package/README.md +8 -6
- package/SECURITY.md +1 -1
- package/dist/capture/export-jpeg.d.ts +19 -0
- package/dist/capture/export-jpeg.js +190 -95
- package/dist/capture/export-jpeg.js.map +1 -1
- package/dist/capture/native-environment-lean.js +19 -9
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/diffraction-provider.js +3 -10
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +1 -1
- package/dist/facade/expected-signal-setup.js +2 -6
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +4 -1
- package/dist/facade/expected-signal.js +151 -31
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/scene-radiance.d.ts +4 -1
- package/dist/facade/scene-radiance.js +4 -4
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +26 -1
- package/dist/facade/simulate-capture.js +151 -26
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +2 -1
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.d.ts +19 -17
- package/dist/optics/diffraction-psf-profile.js +49 -18
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/dist/optics/site-diffraction-kernel.d.ts +5 -0
- package/dist/optics/site-diffraction-kernel.js +32 -0
- package/dist/optics/site-diffraction-kernel.js.map +1 -0
- package/dist/output/print-jpeg.d.ts +28 -1
- package/dist/output/print-jpeg.js +148 -48
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/sensor/stochastic-seed.d.ts +12 -0
- package/dist/sensor/stochastic-seed.js +25 -0
- package/dist/sensor/stochastic-seed.js.map +1 -1
- package/docs/API_REFERENCE.md +8 -1
- package/docs/QUICK_START.md +59 -0
- package/docs/api/capture-export-jpeg.md +10 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +37 -2
- package/docs/api/facade-scene-radiance.md +2 -1
- package/docs/api/facade-simulate-capture.md +28 -0
- package/docs/api/optics-diffraction-psf-profile.md +1 -1
- package/docs/api/output-print-jpeg.md +41 -0
- package/package.json +1 -1
package/docs/QUICK_START.md
CHANGED
|
@@ -1151,6 +1151,65 @@ strips.forEach((strip, i) => stitched.set(strip.image.samples as Uint8Array, i *
|
|
|
1151
1151
|
console.log(strips[0]!.region, stitched.length);
|
|
1152
1152
|
```
|
|
1153
1153
|
|
|
1154
|
+
### A whole-frame DNG from parts
|
|
1155
|
+
|
|
1156
|
+
`serializeSimulatedCaptureDngFromParts(options, parts)` writes the whole-frame DNG of `options` (`output.format: "dng"`,
|
|
1157
|
+
no `output.region`) from region renders of the same request, so a large DNG's RAW can render in parallel like a JPEG or PNG.
|
|
1158
|
+
Each part is a region result's `raw` and `region` (any region format; `result.raw` holds the region's native sites,
|
|
1159
|
+
`result.region.nativeRect` where they lie). A region's sites are the whole render's sites, so the joined RAW is the whole
|
|
1160
|
+
render's RAW; the function then resolves the request and writes the DNG exactly as `simulateCapture` does, and the bytes
|
|
1161
|
+
are the same. The parts must cover the frame's native raster; where they overlap (the demosaic margin) their sites must
|
|
1162
|
+
agree, and a part from another request is refused. Render the parts with a fixed white balance and without `output.dng`
|
|
1163
|
+
(a region takes neither; the RAW codes do not depend on the white-balance intent): auto white balance and the capture time
|
|
1164
|
+
apply to the DNG options passed here, and auto white balance is measured over the joined frame.
|
|
1165
|
+
|
|
1166
|
+
Tests compare the joined DNG byte for byte with the whole-frame DNG for bilinear and AHD, diffraction with the four-spot
|
|
1167
|
+
filter and the 2 × 2 box, auto white balance with a capture time, a reflectance-function scene and the environment path.
|
|
1168
|
+
On the Photivra app's 24 MP request (6000 × 4000 at 6 µm, 24 parts of about 1024 pixels; Node, one thread) the DNG was
|
|
1169
|
+
the same bytes both ways; the parts took 58.1 s of CPU against 50.6 s for one whole render (Quality options: 119.3 s against
|
|
1170
|
+
113.6 s) and the join 2.6 s, so on four workers the wall time is roughly a quarter of the whole render's.
|
|
1171
|
+
|
|
1172
|
+
```ts
|
|
1173
|
+
import { serializeSimulatedCaptureDngFromParts, simulateCapture, type SimulateCaptureDngPart, type SimulateCaptureOptions } from "@photivra/engine";
|
|
1174
|
+
|
|
1175
|
+
const dngFrame: SimulateCaptureOptions = { scene: { preset: "siemens-star" }, output: { width: 96, height: 64, format: "dng" } };
|
|
1176
|
+
// Two strips rendered as sRGB regions (in an application, in separate Web Workers), then one DNG of the whole frame.
|
|
1177
|
+
const parts: SimulateCaptureDngPart[] = await Promise.all([0, 32].map(async (y) => {
|
|
1178
|
+
const part = await simulateCapture({ ...dngFrame, output: { width: 96, height: 64, region: { x: 0, y, width: 96, height: 32 } } });
|
|
1179
|
+
return { raw: part.raw, region: part.region! };
|
|
1180
|
+
}));
|
|
1181
|
+
const dng = await serializeSimulatedCaptureDngFromParts(dngFrame, parts);
|
|
1182
|
+
console.log(dng.bytes.length);
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
### A Print JPEG written in strips
|
|
1186
|
+
|
|
1187
|
+
At 1:1 (output size equal to the crop), a Print JPEG's rows can be coded on separate workers.
|
|
1188
|
+
`createPrintJpegStrip(input, { y, height, samples })` codes crop rows `y` to `y + height` (8-bit sRGB RGB, crop-wide;
|
|
1189
|
+
`y` a multiple of 8, and `height` too unless the strip ends at the image's bottom) apart from the rows before them.
|
|
1190
|
+
`composePrintJpegStrips(input, strips)` joins strips that cover the image, top to bottom, into the file
|
|
1191
|
+
`createPrintJpegTask` writes for the same plan and source pixels: the same bytes. Only the first block of each strip
|
|
1192
|
+
depends on the strip before it (its DC differences), so the join codes that block and copies the rest of the strip's
|
|
1193
|
+
bits. The output's `providerReads` is 0, because no source is read.
|
|
1194
|
+
|
|
1195
|
+
Tests compare the joined file with the task's file byte for byte, for several splits, crops, quantization steps and a
|
|
1196
|
+
density. For a 24 MP file (Node, one thread), the whole task took 8.1 s; four strips took 1.9 s each, and the join took
|
|
1197
|
+
1.1 s (a 64 MB file). In the Photivra app (Chromium, four workers), writing a 24 MP JPEG takes about 3 s instead of 9–12 s.
|
|
1198
|
+
|
|
1199
|
+
```ts
|
|
1200
|
+
import { composePrintJpegStrips, createPrintJpegStrip, type PrintJpegInput } from "@photivra/engine";
|
|
1201
|
+
|
|
1202
|
+
const width = 64, height = 40, pixels = new Uint8Array(width * height * 3).fill(120);
|
|
1203
|
+
const jpegPlan: PrintJpegInput = {
|
|
1204
|
+
source: { captureId: "capture", imageStateId: "sdr", artifactId: "pixels", sha256: "0".repeat(64), width, height, encoding: "encoded-srgb-8-rgb" },
|
|
1205
|
+
crop: { x: 0, y: 0, width, height }, outputWidth: width, outputHeight: height, quantizationStep: 2, maximumEncodedBytes: 1_000_000, maximumProviderReads: 16
|
|
1206
|
+
};
|
|
1207
|
+
// Two strips (in an application, coded in separate Web Workers), then the file.
|
|
1208
|
+
const strips = [[0, 24], [24, 16]].map(([y, rows]) => createPrintJpegStrip(jpegPlan, { y: y!, height: rows!, samples: pixels.slice(y! * width * 3, (y! + rows!) * width * 3) }));
|
|
1209
|
+
const jpeg = composePrintJpegStrips(jpegPlan, strips);
|
|
1210
|
+
console.log(jpeg.bytes.length);
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1154
1213
|
## Ideal reference (2.3)
|
|
1155
1214
|
|
|
1156
1215
|
`simulateIdealReference(options)` takes the same options as `simulateCapture` and returns what the camera
|
|
@@ -14,3 +14,13 @@ was exactly a negative half-integer. The repository's test images are unchanged.
|
|
|
14
14
|
```ts
|
|
15
15
|
EXPORT_JPEG_ENCODER_ID = "photivra-baseline-jpeg-444-v2" as const
|
|
16
16
|
```
|
|
17
|
+
|
|
18
|
+
## JpegStripBits
|
|
19
|
+
|
|
20
|
+
The entropy-coded bits of consecutive blocks of one image, coded apart from the blocks before them (`encodeJpegStrip`):
|
|
21
|
+
the first block's quantized coefficients (3 × 64, natural order), whose DC differences need the block before; the other
|
|
22
|
+
blocks' bits, unstuffed, most-significant first; and the last block's DC values, which the next strip's first block needs.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
export interface JpegStripBits { first: Float64Array; bits: Uint8Array; bitLength: number; blockCount: number; lastDc: Float64Array }
|
|
26
|
+
```
|
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.9.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.9.0",
|
|
3
|
+
"engineApiVersion": "2.9.0",
|
|
4
4
|
"exports": [
|
|
5
5
|
{
|
|
6
6
|
"name": "AbsoluteUncertaintyEstimate",
|
|
@@ -2097,6 +2097,11 @@
|
|
|
2097
2097
|
"source": "src/capture/native-environment-raw.ts",
|
|
2098
2098
|
"kind": "runtime"
|
|
2099
2099
|
},
|
|
2100
|
+
{
|
|
2101
|
+
"name": "composePrintJpegStrips",
|
|
2102
|
+
"source": "src/output/print-jpeg.ts",
|
|
2103
|
+
"kind": "runtime"
|
|
2104
|
+
},
|
|
2100
2105
|
{
|
|
2101
2106
|
"name": "composeRelativeErrorBounds",
|
|
2102
2107
|
"source": "src/gpu/image-kernel-execution.ts",
|
|
@@ -2377,6 +2382,11 @@
|
|
|
2377
2382
|
"source": "src/color/illuminants.ts",
|
|
2378
2383
|
"kind": "runtime"
|
|
2379
2384
|
},
|
|
2385
|
+
{
|
|
2386
|
+
"name": "createPrintJpegStrip",
|
|
2387
|
+
"source": "src/output/print-jpeg.ts",
|
|
2388
|
+
"kind": "runtime"
|
|
2389
|
+
},
|
|
2380
2390
|
{
|
|
2381
2391
|
"name": "createPrintJpegTask",
|
|
2382
2392
|
"source": "src/output/print-jpeg.ts",
|
|
@@ -3882,6 +3892,11 @@
|
|
|
3882
3892
|
"source": "src/equipment/iso-capabilities.ts",
|
|
3883
3893
|
"kind": "type"
|
|
3884
3894
|
},
|
|
3895
|
+
{
|
|
3896
|
+
"name": "JpegStripBits",
|
|
3897
|
+
"source": "src/capture/export-jpeg.ts",
|
|
3898
|
+
"kind": "type"
|
|
3899
|
+
},
|
|
3885
3900
|
{
|
|
3886
3901
|
"name": "LateralCaLensCorrection",
|
|
3887
3902
|
"source": "src/output/lens-corrections.ts",
|
|
@@ -5522,6 +5537,16 @@
|
|
|
5522
5537
|
"source": "src/output/print-jpeg.ts",
|
|
5523
5538
|
"kind": "type"
|
|
5524
5539
|
},
|
|
5540
|
+
{
|
|
5541
|
+
"name": "PrintJpegStrip",
|
|
5542
|
+
"source": "src/output/print-jpeg.ts",
|
|
5543
|
+
"kind": "type"
|
|
5544
|
+
},
|
|
5545
|
+
{
|
|
5546
|
+
"name": "PrintJpegStripInput",
|
|
5547
|
+
"source": "src/output/print-jpeg.ts",
|
|
5548
|
+
"kind": "type"
|
|
5549
|
+
},
|
|
5525
5550
|
{
|
|
5526
5551
|
"name": "PrintJpegTask",
|
|
5527
5552
|
"source": "src/output/print-jpeg.ts",
|
|
@@ -7867,6 +7892,11 @@
|
|
|
7867
7892
|
"source": "src/capture/simulated-capture.ts",
|
|
7868
7893
|
"kind": "runtime"
|
|
7869
7894
|
},
|
|
7895
|
+
{
|
|
7896
|
+
"name": "serializeSimulatedCaptureDngFromParts",
|
|
7897
|
+
"source": "src/facade/simulate-capture.ts",
|
|
7898
|
+
"kind": "runtime"
|
|
7899
|
+
},
|
|
7870
7900
|
{
|
|
7871
7901
|
"name": "setExposureCompensationOnMeterTarget",
|
|
7872
7902
|
"source": "src/exposure/metering-target.ts",
|
|
@@ -7977,6 +8007,11 @@
|
|
|
7977
8007
|
"source": "src/facade/simulate-capture.ts",
|
|
7978
8008
|
"kind": "type"
|
|
7979
8009
|
},
|
|
8010
|
+
{
|
|
8011
|
+
"name": "SimulateCaptureDngPart",
|
|
8012
|
+
"source": "src/facade/simulate-capture.ts",
|
|
8013
|
+
"kind": "type"
|
|
8014
|
+
},
|
|
7980
8015
|
{
|
|
7981
8016
|
"name": "SimulateCaptureExposureMode",
|
|
7982
8017
|
"source": "src/facade/simulate-capture.ts",
|
|
@@ -175,7 +175,8 @@ export type SceneMaterialPoint = Omit<ScenePlanePoint, "wavelengthNanometers">;
|
|
|
175
175
|
|
|
176
176
|
## ScenePlanePoint
|
|
177
177
|
|
|
178
|
-
A point of the scene plane passed to caller scene functions
|
|
178
|
+
A point of the scene plane passed to caller scene functions: a new object for every call, which the engine does not read
|
|
179
|
+
after the call (not frozen: freezing cost about 40 % of a chart lookup).
|
|
179
180
|
|
|
180
181
|
```ts
|
|
181
182
|
export interface ScenePlanePoint {
|
|
@@ -62,6 +62,23 @@ export interface IdealReferenceResult {
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## serializeSimulatedCaptureDngFromParts
|
|
66
|
+
|
|
67
|
+
Writes the whole-frame DNG of `options` (`output.format: "dng"`, no `output.region`) from the packed RAW of region renders of
|
|
68
|
+
the same request (#391), so the RAW can render in parallel. A region render's native sites are the same sites as the whole
|
|
69
|
+
render's (each site's seeds come from its index in the whole raster), so the joined RAW is the whole render's RAW, and the
|
|
70
|
+
request is resolved and written exactly as `simulateCapture` writes its DNG: the bytes are the same. The parts must cover
|
|
71
|
+
every native site of the frame; where they overlap (each region carries the demosaic margin) their sites must agree. Region
|
|
72
|
+
renders of the request with any format serve (a region's own `output.format` does not change its RAW). A region cannot take
|
|
73
|
+
`output.whiteBalance: "auto"` or `output.dng`: render the parts with a fixed white balance (the RAW codes do not depend on the
|
|
74
|
+
white-balance intent) and pass the DNG's own options here, where auto white balance is measured over the joined frame.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
export async function serializeSimulatedCaptureDngFromParts(options: SimulateCaptureOptions, parts: readonly SimulateCaptureDngPart[]): Promise<NativeRawDng>;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This call is asynchronous; byte hashing uses the caller runtime's Web Crypto.
|
|
81
|
+
|
|
65
82
|
## SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES
|
|
66
83
|
|
|
67
84
|
Default midpoint samples per axis of `sensor.pixelAperture: "box"`.
|
|
@@ -176,6 +193,17 @@ edge-directed method that reduces zipper and false colour on sharp edges.
|
|
|
176
193
|
export type SimulateCaptureDemosaic = "bilinear" | "ahd";
|
|
177
194
|
```
|
|
178
195
|
|
|
196
|
+
## SimulateCaptureDngPart
|
|
197
|
+
|
|
198
|
+
One part of a whole-frame DNG (#391): a region render's packed RAW and where its native sites lie (`result.raw`, `result.region`).
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
export interface SimulateCaptureDngPart {
|
|
202
|
+
raw: Pick<NativeRawOutput, "codes" | "blackLevels" | "digitalSaturationCodes" | "saturationFlags" | "effectiveSaturationCodes">;
|
|
203
|
+
region: { nativeRect: { x: number; y: number; width: number; height: number } };
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
179
207
|
## SimulateCaptureExposureMode
|
|
180
208
|
|
|
181
209
|
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
@@ -72,7 +72,7 @@ export interface DiffractionLimitedPsfProfile {
|
|
|
72
72
|
/** Largest truncated energy over the wavelengths. */
|
|
73
73
|
maximumTruncatedEnergyFraction: number;
|
|
74
74
|
/**
|
|
75
|
-
* How the cells were integrated. 2.5: wavelengths with β = p·f_c ≥
|
|
75
|
+
* How the cells were integrated. 2.5: wavelengths with β = p·f_c ≥ `AIRY_REAL_SPACE_MINIMUM_BETA` (192 in 2.5–2.8.1, then 32) use the real-space
|
|
76
76
|
* encircled-energy method; `realSpaceWavelengthsNm` lists them, and `method` is `"frequency-domain-and-real-space"` when both ran.
|
|
77
77
|
* `maximumRadialNodes` counts frequency-domain radial nodes (0 when only the real-space method ran); `maximumAngularNodes` the
|
|
78
78
|
* angular nodes of either method (for the real-space method, all θ nodes of one wavelength).
|
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
+
## composePrintJpegStrips
|
|
6
|
+
|
|
7
|
+
Joins the strips of a 1:1 Print JPEG (`createPrintJpegStrip`), covering the image top to bottom in order, into its file:
|
|
8
|
+
the same bytes `createPrintJpegTask` writes for the plan and the same source pixels. No source is read, so the output's
|
|
9
|
+
`providerReads` is 0.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
export function composePrintJpegStrips(input:PrintJpegInput,strips:readonly PrintJpegStrip[]):PrintJpegOutput;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This call is synchronous; any supplied provider must follow its explicit synchronous contract.
|
|
16
|
+
|
|
17
|
+
## createPrintJpegStrip
|
|
18
|
+
|
|
19
|
+
Codes rows of a 1:1 Print JPEG (output size equal to the crop) apart from the rows before them, so a large file's strips
|
|
20
|
+
can be written on separate workers. `strip.y` is a multiple of 8 and `strip.height` a multiple of 8 unless the strip ends at
|
|
21
|
+
the image's bottom; `strip.samples` are those crop rows' 8-bit sRGB RGB, crop-wide. `composePrintJpegStrips` joins strips that
|
|
22
|
+
cover the image, in order, into the bytes `createPrintJpegTask` writes for the same plan and source pixels.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
export function createPrintJpegStrip(input:PrintJpegInput,strip:PrintJpegStripInput):PrintJpegStrip;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This call is synchronous; any supplied provider must follow its explicit synchronous contract.
|
|
29
|
+
|
|
5
30
|
## createPrintJpegTask
|
|
6
31
|
|
|
7
32
|
Creates the export task. Filtering weights each source pixel by its exact area overlap, after decoding the declared 8-bit SDR source.
|
|
@@ -123,6 +148,22 @@ export interface PrintJpegSource {
|
|
|
123
148
|
}
|
|
124
149
|
```
|
|
125
150
|
|
|
151
|
+
## PrintJpegStrip
|
|
152
|
+
|
|
153
|
+
A strip of a 1:1 Print JPEG's coded blocks (`createPrintJpegStrip`), for `composePrintJpegStrips`.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
export interface PrintJpegStrip {readonly y:number;readonly height:number;readonly width:number;readonly quantizationStep:number;readonly bits:JpegStripBits}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## PrintJpegStripInput
|
|
160
|
+
|
|
161
|
+
Rows of a 1:1 Print JPEG for `createPrintJpegStrip`: crop rows `y` to `y + height` (relative to the crop), crop-wide RGB.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
export interface PrintJpegStripInput {y:number;height:number;samples:Uint8Array}
|
|
165
|
+
```
|
|
166
|
+
|
|
126
167
|
## PrintJpegTask
|
|
127
168
|
|
|
128
169
|
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@photivra/engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Scientific TypeScript engine for camera optics, exposure, focus, motion, sensor sampling, diffraction, and digital imaging calculations.",
|
|
6
6
|
"type": "module",
|